fix(llmagent): do not close shared cached httpx clients in cleanup() #58
No reviewers
Labels
No labels
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#57 LLMAgent.cleanup() closes shared cached httpx clients, breaking subsequent LLM requests
cleveragents/cleveractors-core
Reference
cleveragents/cleveractors-core!58
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bugfix/m1-llmagent-cleanup-shared-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes #57 —
LLMAgent.cleanup()was closing provider SDK client attributes (root_async_client,root_client,_async_client,_client) on every agent teardown.langchain-anthropicandlangchain-openaicache their default httpx clients via module-levellru_cachefunctions. Closing those clients poisoned the cache: every subsequentChatAnthropic/ChatOpenAIinstance in the same process received the same closed httpx client and failed with a connection error.This was the root cause of the downstream webapp failure where one actor request broke all subsequent LLM requests in the same process.
Changes
Fix (
src/cleveractors/agents/llm.py)LLMAgent.cleanup()now only setsself._chat_model = None— it does not callclose()on any provider SDK client attribute._KNOWN_CLIENT_ATTRSclass variable (no longer needed).ClassVarfrom thetypingimport.cleanup()docstring to explain why closing is wrong (sharedlru_cacheclients)._chat_model.Regression tests (
features/llm_cleanup_shared_client.feature)Six new BDD scenarios tagged
@tdd_issue @tdd_issue_57:_async_clientpath:close()not called on mock SDK client._clientpath: realhttpx.Clientnot closed.root_async_clientpath:close()not called on mock SDK client.root_clientpath: realhttpx.Clientnot closed.Obsolete tests removed
credential_injection.featurethat tested the old (buggy) close-on-cleanup behaviour.llm_missing_coverage.featurecleanup scenarios to assert clients are not closed.credential_cleanup_steps.py(cleanup-error steps deleted; onlyresolve_class_refpatch step remains).llm_missing_coverage_steps.pywith corrected assertions.Changelog
CHANGELOG.md.Quality gate results
nox -s lintnox -s typechecknox -s unit_testsnox -s integration_testsnox -s coverage_reportnox -s security_scannox -s dead_codeCloses #57
Review: Approve ✅
The fix is correct, minimal, and well-targeted.
LLMAgent.cleanup()no longer closes provider SDK client attributes that are backed by sharedlru_cacheclients, eliminating the cross-request cache poisoning that broke subsequent LLM calls. The regression test coverage is appropriate (4 attribute paths × 2 providers, plus 2 end-to-end two-agent scenarios), the obsolete cleanup-error scenarios are correctly removed, and the changelog is updated. Coverage remains at 97.1%.Critical Issues
None.
Major Issues
None.
Minor Issues (non-blocking)
step_ml_setup_agent_with_failing_async_clientinfeatures/steps/llm_missing_coverage_steps.pyremains after its Gherkin scenario was removed._cleanup_log_handlerinfeatures/environment.pyis now unreachable after its installer was removed.src/cleveractors/runtime.py(line 197) still describescleanup()as closinghttpx.AsyncClientinstances.Nits (non-blocking)
LLMAgent.cleanup()— original rationale (preventing double-close()) no longer applies.scc_tracked_attrin cleanup shared-client step file.These housekeeping items can be addressed in this PR as a polish pass or deferred to a follow-up. None blocks the merge. Approve.
Review verdict: Approve with minor, non-blocking cleanup notes
I reviewed this PR directly (without using the
rui-review-pragent). Assessment below is against the two requested checks: (1) whether the changes implement what the ticket requires, and (2) whether they break anything.1. Does the change implement what the ticket requires?
Yes. Issue #57 asks that
LLMAgent.cleanup()stop closing provider SDK client attributes backed by sharedlru_cachehttpx clients, and instead only release the agent's own chat-model reference.The change in
src/cleveractors/agents/llm.pydoes exactly that:_KNOWN_CLIENT_ATTRSis removed.ClassVarimport is removed.cleanup()now only acquires the lock and setsself._chat_model = None.lru_cacheclients inlangchain-anthropic/langchain-openai).The regression tests in
features/llm_cleanup_shared_client.featurecover the four client attribute paths from the issue (_async_client,_client,root_async_client,root_client) plus two end-to-end two-agent scenarios. The affected existing feature files were updated to assert clients are not closed, and the obsolete cleanup-error scenarios were removed. TheCHANGELOG.mdentry is accurate and complete.2. Do the changes break anything?
No. I ran the relevant quality gates and test suites independently:
nox -s unit_testsnox -s unit_tests -- features/llm_cleanup_shared_client.featurenox -s unit_tests -- features/credential_injection.feature features/llm_missing_coverage.featurenox -s unit_tests -- features/actor_result_token_counting.feature features/execute_stream.featurenox -s lintnox -s typechecklangchain_google_genaiimport warning)nox -s dead_codenox -s security_scanThe full unit suite passing confirms no regressions. The streaming/execution tests are particularly important because they verify
cleanup()is still invoked correctly fromruntime.py/runtime_dispatch.pyeven though its internals changed.Minor housekeeping issues (non-blocking)
I noticed the same cleanup leftovers already flagged in the author's self-review:
step_ml_setup_agent_with_failing_async_clientinfeatures/steps/llm_missing_coverage_steps.pyis no longer used by any Gherkin scenario.features/environment.pystill removes a_cleanup_log_handlerthat nothing installs anymore.src/cleveractors/runtime.pyline 197 still sayscleanup()“closes httpx.AsyncClient instances,” which is no longer true.These are small polish items that do not affect correctness or quality gates, but they should be cleaned up either in this PR or a follow-up to avoid confusing future maintainers.
Overall: Approve. The fix is correct, minimal, and directly addresses the root cause described in #57. Regression coverage is appropriate and the full test suite passes without regressions.