Skip to content

fix(code): keep MCP shutdown-race traceback off the terminal - #5325

Merged
Mason Daugherty (mdrxy) merged 4 commits into
mainfrom
mdrxy/code/quiet-mcp-shutdown-log
Aug 5, 2026
Merged

fix(code): keep MCP shutdown-race traceback off the terminal#5325
Mason Daugherty (mdrxy) merged 4 commits into
mainfrom
mdrxy/code/quiet-mcp-shutdown-log

Conversation

@mdrxy

@mdrxy Mason Daugherty (mdrxy) commented Aug 5, 2026

Copy link
Copy Markdown
Member

Quitting dcode while an MCP response is in flight no longer prints an anyio.ClosedResourceError traceback to the terminal on exit.


An MCP transport's reader task can still be running while the session stream is torn down. Its read_stream_writer.send(...) raises anyio.ClosedResourceError, the SDK's surrounding except calls logger.exception(...), and since nothing handles the mcp hierarchy the record clears logging.lastResort's WARNING threshold and prints a full traceback — alarming-looking but harmless, since the app was already exiting.

A logging.Filter on each transport's own logger drops those records. It has to live on the emitting logger: logger-level filters never run for records propagated up from children.

_MCP_SHUTDOWN_RACE_MESSAGES lists the messages whose try block wraps a read-stream send, and a record is dropped only if it carries an anyio closed/broken-resource error:

Logger Message
mcp.client.streamable_http Error parsing JSON response (single JSON body)
mcp.client.streamable_http Error parsing SSE message (streamed response)
mcp.client.sse Error in sse_reader

Error in post_writer is excluded — it wraps the entire write loop, so a closed stream there can also mean the transport was orphaned while the session was live.

Why this is safe. The in-flight request still fails loudly: the session's receive loop hands pending response streams an ErrorData(CONNECTION_CLOSED) on its way out, so send_request raises McpError("Connection closed"). A network-level drop can't reach the filter — httpcore remaps anyio's stream errors onto httpx.ReadError and friends before httpx re-raises, and the filter never walks __cause__. For a BaseExceptionGroup, every leaf must be a teardown, so a group carrying a real fault stays visible. The mcp hierarchy gets no NullHandler (pinned by a test), so everything else reaches stderr.

Debug mode. With DEEPAGENTS_CODE_DEBUG set, an installed filter is removed and the transport loggers are routed to the debug log. Both halves matter: the filter would otherwise suppress the record for the debug file handler too, and without that handler it would fall through to lastResort and print over the TUI.

Drift-pin tests read the logger names off the installed mcp modules — mirroring the existing genai-prices pin — so an upstream rename fails in CI rather than in a user's terminal.

Quitting dcode while an HTTP MCP response is in flight races the
streamable-HTTP transport's response task: the session stream is already
closed, send() raises anyio.ClosedResourceError, and the MCP SDK logs
logger.exception("Error parsing JSON response"). With no handler on the
mcp logger hierarchy, the traceback clears logging.lastResort's WARNING
threshold and prints over the terminal.

Add "mcp" to _QUIET_SDK_LOGGER_NAMES so MCP diagnostics route to the
debug log when DEEPAGENTS_CODE_DEBUG is set and are swallowed via
NullHandler otherwise, matching the existing genai-prices/langchain/
langsmith treatment. Pin the coupling with a test that reads the logger
name off the installed mcp.client.streamable_http module.
@github-actions github-actions Bot added dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: XS < 50 LOC labels Aug 5, 2026

@open-swe open-swe Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Open SWE Review: No issues found

Open SWE reviewed this PR and found no potential bugs to report.

Open in WebView Open SWE trace

@github-actions github-actions Bot added size: S 50-199 LOC and removed size: XS < 50 LOC labels Aug 5, 2026
@github-actions github-actions Bot added size: M 200-499 LOC and removed size: S 50-199 LOC labels Aug 5, 2026
The shutdown-race filter matched only `Error parsing JSON response` on
`mcp.client.streamable_http`, which misses the common case. A server that
streams its response answers through `_handle_sse_event`, whose `try` also
wraps the read-stream `send` and logs `Error parsing SSE message`; the plain
SSE transport races the same way in `sse_reader`. Drive the match from a
per-logger message map so all three are covered, and keep
`Error in post_writer` out of it -- that one wraps the whole write loop, so a
closed stream there can mean the transport was orphaned mid-session.

Require *every* leaf of a `BaseExceptionGroup` to be a stream teardown before
dropping the record. Matching on any leaf suppressed groups that also carried
a real fault, which was the only actionable thing in them.

Resolve anyio's exception types through a cached helper instead of importing
inside the filter body. `logging` swallows exceptions from `Handler.emit` but
not from `Logger.filter`, so an `ImportError` there would have propagated into
the transport's `except` block; the guarded import degrades to keeping the
record, and caching keeps `anyio` off the startup path.

Correct the docstrings. The dropped record is not redundant because the
transport re-sends the exception -- that `send` is what raised, so the retry
raises too. What actually surfaces the failure is the session receive loop
handing pending streams an `ErrorData(CONNECTION_CLOSED)`.

Tests: restore the transport loggers' filters via `monkeypatch.setattr` rather
than leaving the process-global loggers stripped, pin that `mcp` never enters
`_QUIET_SDK_LOGGER_NAMES` (a `NullHandler` there would skip `lastResort` and
hide every transport diagnostic), and add an end-to-end case that logs through
the real logger so a filter on the wrong target fails.
@mdrxy
Mason Daugherty (mdrxy) merged commit 7d97097 into main Aug 5, 2026
85 checks passed
@mdrxy
Mason Daugherty (mdrxy) deleted the mdrxy/code/quiet-mcp-shutdown-log branch August 5, 2026 22:59
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: M 200-499 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant