fix(code): stop leaking turn coroutines and sqlite handles - #5218
Merged
Conversation
Two teardown leaks surfaced as pytest warnings. The agent turn was handed to `run_worker` as a pre-built coroutine, which a worker cancelled before its first event-loop step never awaits. And `aiosqlite` only records its sqlite handle when the awaiting coroutine resumes, so a background worker cancelled mid-connect stranded the handle for the garbage collector. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
marked this pull request as ready for review
July 31, 2026 05:23
The handle guard only covered a cancel arriving while the worker thread was still opening the database. A cancel arriving after the handle was delivered but before the awaiting coroutine resumed still leaked it: aiosqlite drops its own reference before the cleanup it queued can run. Queue the close ahead of that cleanup so both halves of the window are covered. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy)
pushed a commit
that referenced
this pull request
Aug 6, 2026
> [!CAUTION] > Merging this PR will automatically publish to **PyPI** and create a **GitHub release**. For the full release process, see [`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md). --- _Release notes preview: keep this section in sync with the package `CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`, not this PR description — keep them aligned anyway so the PR stays an accurate historical record for reviewers and anyone returning later._ --- ## [0.1.53](deepagents-code==0.1.52...deepagents-code==0.1.53) (2026-08-06) ### Features - Added pricing coverage with Baseten built-in overrides and local fallback overrides when `genai-prices` is missing data ([#5312](#5312), [#5304](#5304)). - Suggest compacting large resumed threads ([#5318](#5318)). - Added terminal program trace metadata ([#5329](#5329)). ### Bug Fixes - Preserved runtime offload archive routing ([#5328](#5328)). - Always restart after a successful startup auto-update ([#5317](#5317)). - Fixed leaked turn coroutines and SQLite handles ([#5218](#5218)). - Keep MCP shutdown-race tracebacks from appearing in the terminal ([#5325](#5325)). - Open the `/auto model` selector immediately while connecting ([#5341](#5341)). - Route failures to `PostToolUseFailure` ([#5315](#5315)). - Use dismissed copy for ask-user prompts ([#5331](#5331)). _End release notes preview._ --- > [!NOTE] > A **New Contributors** section is appended to the GitHub release notes automatically at publish time (see [Release Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline), step 2). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interrupting a turn, or exiting while a background thread-cache refresh is in flight, no longer strands an unawaited coroutine or an open SQLite handle.
The unit suite emitted three families of warnings, all at teardown. Two are ours and are fixed here; the third is upstream and is filtered.
An unawaited turn coroutine
_send_to_agenthandedrun_workeran already-built_run_agent_taskcoroutine. Textual never runs the work of a worker cancelled before its first event-loop step, so that coroutine was finalized unawaited — aRuntimeWarning, and (once interpreter teardown had gone far enough to break the import machinery its cleanup relies on) an unraisableKeyError: '__import__'. Passing a callable means the coroutine only exists if the worker actually runs, so there is nothing to strand. Several tests were closing that coroutine by hand purely to silence the warning; those workarounds are gone, and a new test pins the callable contract.Where it started: #5196. Handing
run_workera coroutine dates all the way back to the original Textual REPL (#686), but it was harmless until something cancelled a worker before its first step. #5196 added the recovery path for exactly that situation along with the tests that exercise it. Its parent commit runs the app test module with zeronever awaitedwarnings; #5196 itself produces three.An unclosed SQLite handle
aiosqliteopens the database on its worker thread and hands the rawsqlite3.Connectionback through a future, recording it on the connection only when the awaiting coroutine resumes. A cancel landing anywhere in that window left the handle unreachable from the cleanup that follows, so the garbage collector reportedResourceWarning: unclosed database. There are two halves to the window, and both are now covered:Both closes run on the thread that opened the handle, and closing twice is a no-op, so neither disturbs a normal shutdown.
get_checkpointerbuilds its connection through the same helper rather thanAsyncSqliteSaver.from_conn_string, so it gets the same guard.Where it started: #5174. The prewarm that reads the session database has existed since #1481, but it ran once at startup, so it had normally finished before anything cancelled it. #5174 re-fires it after every turn, which reliably leaves a session-DB read in flight when a test app exits. Counting handles that
aiosqliteopened and never closed across the goal-command tests: zero on the parent commit, fourteen on #5174.A
typingdeprecation fromgoogle-genaigoogle.genai.typesbuilds a union alias out oftyping._UnionGenericAlias, which CPython 3.14 deprecates, and it fires at import before any of this package's code runs. This one is not a regression from any change here —deepagents-codehas been tested on the 3.14 leg since the package was created in #3027, and the warning appears wherever a test imports the Google integration. It is tracked upstream as googleapis/python-genai#1640 and still unfixed as ofgoogle-genai2.13.0, so it is filtered narrowly (message, category, and module) rather than worked around.Made by Open SWE