Repository navigation
fix(http): plugins use the pooled client, and a ratchet watches the raw sessions outside the backend (#12979) - #17973
Conversation
…aw sessions outside the backend (#12979) Measured on origin/main at 0a4cffc with an AST walker: 59 aiohttp.ClientSession constructions in 36 non-test files, not the 134/127 the issue records. 12 of those are inside autobot-backend/, where #12979's batches 4-9 already drained the convertible tail and test_raw_client_session_ceiling_12992.py ratchets what is left. The other 47 were under no guard at all. Converted 9 of the 47, in the two core plugins. Every one was a plain `async with aiohttp.ClientSession() as session:` against a module-constant public host, with no connector, no TLS context and no lifetime past the statement, so routing them through get_http_client().tracked_request() changes nothing but the pooling and the active-request accounting. The video provider tests now double HTTPClientManager instead of the aiohttp module; the queued post=/get= shape is unchanged, so the 13 test bodies read as before. Left the rest, with reasons. Every remaining autobot-backend/ site is already a documented carve-out: an SSRF-pinned connector the pooled client cannot accept per request, a custom TLS context, or a session held open across calls. autobot_shared/paperclip_client.py is the same long-lived shape. The 23 sites in the infrastructure operator scripts are unconverted work rather than carve-outs, and the inventory says so rather than inventing a justification for them. The guard is an inventory, not a count. 35 sites in 21 files, compared as a set so two populations cannot agree on a total and differ in membership. #17970's hole -- a baseline and its measurement edited together passing every check -- is closed by requiring each entry to satisfy a structural predicate read from the code: a per-request session in an ordinary library module matches no category, so no number written in the inventory admits it. Detection is AST, never text, because the string appears in docstrings and templates here and a text guard is satisfied by a comment carrying it; the contrast file asserts that directly.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 35 minutes. View limit details
📝 Walkthrough
Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to Invalid timeout settings can leave generation requests without their intended deadlines. Validate those settings before merging; also fix the configuration-sensitive test and inventory check. Pre-merge checks |
|
… of the pooled 10s read cap (#12979)
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml
- Review profile: ASSERTIVE
- Plan: Advanced
- Run ID:
55a82ef0-87e8-4bc8-807f-bbf28b3ab0e2
📒 Files selected for processing (10)
autobot_shared/env_registry_ai.pyautobot_shared/generation_http_timeouts.pydocs/developer/ENV_VARS.mdplugins/core-plugins/image-generation-plugin/tools/generate_image.pyplugins/core-plugins/image-generation-plugin/tools/generate_image_test.pyplugins/core-plugins/video-generation-plugin/tools/providers.pyplugins/core-plugins/video-generation-plugin/tools/providers_test.pyrepo_tests/_raw_aiohttp_session.pyrepo_tests/raw_aiohttp_session_contrast_test.pyrepo_tests/raw_aiohttp_session_inventory_12979_test.py
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
… tests, route inventory through tracked_paths (#12979)
Thinking Path
The issue records 134/127 per-request constructions. That number is stale, and the first job was to replace it rather than quote it.
Measured with an AST walker against
origin/mainat0a4cffced, over tracked*.py:git grep -c 'aiohttp\.ClientSession('ast.Callwhose func is*.ClientSessionor bareClientSession)The 16-occurrence gap between grep and AST is prose — docstrings, an emitted log string and a regex replacement template. That gap is the whole reason this guard parses rather than matches.
Of the 59 non-test constructions, 12 are inside
autobot-backend/, whichautobot-backend/tests/test_raw_client_session_ceiling_12992.pyalready ratchets at 13. Reading that file changed the plan: #12979's batches 4–9 drained the backend's convertible tail, and every one of the 12 is already a documented carve-out —knowledge/connectors/oauth_flow.py:188,services/npu_client.py:_get_session,services/npu_pipeline/dispatcher.pyandnpu_client.py, andskills/sync/mcp_transport.pyeach carry an in-code#12979note;api/provider_auth.py(2),api/marketplace_sources.py,content_reach/_url_guard.pyandagent_loop/search/config_declared_provider.pypass an SSRF-pinnedconnector=;services/slm_client.pyandorchestration/dag_executor.pypass a custom TLS context. None of those can go through the pool:HTTPClientManagerowns one sharedTCPConnectorandsession.request()takes no per-request connector override, so converting a pinned site silently drops the pin and reopens #12278.That left the real finding: 47 sites, four fifths of the population, outside
autobot-backend/and under no ratchet at all.So this PR does two things rather than sweeping: it converts the one coherent cluster that is genuinely per-request, and it puts the remainder under a guard that can name what is left.
What Changed
Converted — 9 sites, 2 files, the core plugins. Every one was
async with aiohttp.ClientSession() as session:against a module-constant public host, with no connector, no TLS context and no lifetime past the statement:Timeouts (review fix). The pooled session default is
ClientTimeout(total=30, connect=5, sock_read=10); the raw sessions used aiohttp's 300s default. All 9 converted calls now pass an explicit per-calltimeout=fromautobot_shared/generation_http_timeouts.py: synchronous Stability generation gets 300s total and read (AUTOBOT_GENERATION_REQUEST_TIMEOUT_S); Flux submit/poll and the 6 video submit/poll calls (enqueue-only or status) get 30s (AUTOBOT_GENERATION_POLL_TIMEOUT_S); connect is 10s (AUTOBOT_GENERATION_CONNECT_TIMEOUT_S). Env-backed, registered inenv_registry_ai.py,ENV_VARS.mdregenerated.plugins/core-plugins/video-generation-plugin/tools/providers.py— 6 (Runway, Sora, Kling × submit/poll)plugins/core-plugins/image-generation-plugin/tools/generate_image.py— 3 (Flux submit, Flux poll, Stability)All now use
get_http_client().tracked_request(...), which owns the active-request counter so it cannot be forgotten (#12981). Redirect behaviour is unchanged:guard_egressis deliberately not passed, because these hosts are module constants rather than config- or user-supplied, which is the condition Rule 8 names — andguard_egressforcesallow_redirects=False, which would be a behaviour change dressed as a cleanup. The lazyimport aiohttp/except ImportErrorprobes went with them; the pooled client carries the dependency.providers_test.pynow doublesHTTPClientManagerinstead of stubbing theaiohttpmodule insys.modules. The queuedpost=/get=shape is unchanged, so all 13 test bodies read exactly as before.Left, with the reason stated — recorded in the guard's
INVENTORY, 35 sites in 21 files:entrypoint__main__guard — infrastructure deploy/monitor/diagnose tools and the SLM agent, which run outside the app process and have no pooled client to shareforeign-runtimeautobot-npu-worker/resources/windows-npu-worker/— a separately packaged Windows deployable withoutautobot_sharedon its pathlong-livedautobot_shared/paperclip_client.py— the session is stored on the instance acrossopen()/close(), the same shape as the backend's documented carve-outsdocs-exampledocs/examples/mcp_agent_workflows/base.pyThe inventory does not claim all 35 are deliberate, and says so in its own docstring. The 23 sites in the infrastructure operator scripts are most likely unconverted work. I did not establish that they are carve-outs, so I did not write a justification claiming they are. #12979 stays open; this is
Refs, notCloses.The guard —
repo_tests/_raw_aiohttp_session.py(detector) +raw_aiohttp_session_inventory_12979_test.py(8 tests) +raw_aiohttp_session_contrast_test.py(11 fixtures):test_prose_only_occurrences_are_not_countedasserts a fixture whereaiohttp.ClientSession(appears only in a docstring, a reviewer comment, a template constant, an f-string and a log call.RATCHET_BASELINES.mdrule 4) — two populations can agree on a count and differ in membership.entrypointneeds a real top-level__main__guard,long-livedneeds every site to be stored rather than consumed inline. A per-request session in an ordinary library module matches no category, so no number written in the inventory admits it — demonstrated as mutation M2 below. The honest limit, stated in the docstring: someone could bolt an unused__main__block onto a library module. That is a visible and absurd diff.test_exclusion_rules_mirror_the_backend_siblingloads the backend guard by path and asserts its exclusion rules;test_the_two_scopes_partition_the_tracked_treeasserts the four buckets are disjoint, non-empty and sum to the tracked population, so a rename ofautobot-backend/or a widened exclusion goes red rather than opening a silent gap. Phrasing it as "nothing is uncovered" would have been a tautology — the leftover set is computed from the same predicate the scope uses.git ls-fileshere,rglobthere.One known gap, written down rather than left as a blind spot.
from aiohttp import ClientSession as Sessionis invisible to the detector — it matches the spelling at the call site.git grep -c 'ClientSession as ' -- '*.py'returns rc=1 with no output, so the gap costs nothing today.test_a_renamed_direct_import_is_countedasserts the gap explicitly and tells the next author to tighten it if that spelling ever appears.Verification
Measurement, both instruments, on
origin/mainat0a4cffced:Tests:
Mutation results — the guard was broken on purpose five ways:
async with aiohttp.ClientSession() as s:appended toautobot_shared/network_utils.py(a scoped library module)FAILED test_no_raw_session_outside_the_recorded_inventoryINVENTORYentry for it, inheriting categoryentrypoint— the #17970 two-record holeFAILED test_every_inventory_entry_satisfies_its_declared_categoryproviders.pyFAILED ... new files: ['plugins/core-plugins/video-generation-plugin/tools/providers.py']Pre-push (
PATH=/home/martins/.venv-python-suite/bin:$PATH, no--no-verify, nocore.hooksPathoverride):Two repo hooks caught real defects in this branch before it left the machine, both recorded here rather than quietly fixed: the
print()guard fired on prose in a docstring, andgit-toplevel-env-scrubbedfired because the walker calledgit ls-fileswithoutscrubbed_git_env()— an inheritedGIT_DIRoutrankscwd=and would have enumerated another checkout's index without erroring. Both fixed in the commit.Head history:
3fd9ffa5fe(original push) →b052cc66f2(merge of main) →c56d1a1172(timeout fix) →df7e4ebc0a(rebased onto72273222) →a488c3713e(rebased onto8b7de912) →e32394f869(exact timeout assertions) →bdef94fe3b(rebased onto the bot merge of main, which includes the image mirror workflow; this is the pushed head). →47c9c82354(CI-red and review fixes: positive-finite timeout overrides, plugin tests wired into pytest.ini and the four workflows, inventory test via tracked_paths, main-guard Eq check; local until pushed).Timeout fix verified by
generate_image_test.py(Stability and Flux calls carry aClientTimeout) andproviders_test.py::test_every_request_passes_a_timeout(all 6 video calls, sock_read >= 30).Collision check.
git diff --name-only origin/main...origin/<b>was run for all 14 open PR branches. One of my 39 text-grep candidates appeared:autobot-infrastructure/shared/scripts/phase_validation_system.py, owned by #17952. I did not edit it; its diff on that branch changes noClientSessionconstruction, so the inventory entry (count 2) remains valid. No other file in this PR appears in any open PR.What I could not establish. Whether the 23 infrastructure-script sites are deliberate. They have the structural shape of operator entrypoints, which is what the
entrypointcategory asserts and all it asserts. I did not read each script's lifecycle closely enough to say any of them should stay raw, and I did not write a carve-out comment claiming so.Refs #12979, #12992, #13625, #17970.
Single-issue rationale
This closes nothing, so there is no closing issue to batch with. It is deliberately partial: 9 of 47 sites converted, with the other 35 recorded rather than swept, because #12979's full population is 36 files and no reviewer can honestly pass on that in one diff. The two files it touches outside
repo_tests/are the two core plugins, which no open PR touches. Appending it to any in-flight PR would mix a behaviour change in the outbound-HTTP path with that PR's scope and put a new repo-wide ratchet behind an unrelated review.Model Used
Claude Opus 5 (1M context)
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation