Repository navigation
Conversation
`_run_agent_task_body` enters every `agent_run_context` into a shared
`AsyncExitStack`:
async with AsyncExitStack() as stack:
for cm in run_ctxs:
await stack.enter_async_context(cm)
return await _do_run(prompt_payload)
An MCP server whose subprocess never came up raises from `__aenter__`. Entered
as a single unit, that one failure unwinds the stack and `_do_run` — the model
call — is never reached. A user with six connectors and one broken one gets no
inference at all.
The `except* McpError` handler downstream hides how bad this is. It prints
"An MCP server failed during this run. Run /mcp logs <name> for details" and
returns, which reads as a warning attached to an answer. There is no answer:
the coroutine falls off the end and returns None.
Observed with a connector whose `uvx` dependency resolve failed: the process
existed, so every status flag said RUNNING and the pre-run health checks
passed. It only failed at context-entry time, by which point the all-or-
nothing stack had already made it fatal.
Each context now enters in its own try. A failure is reported by name and the
run continues without that server's tools. A connector is a capability, not a
precondition — losing one should cost its tools, not the answer. The agent
still has its built-in file and shell tools, which is most of what it does.
Servers that DID enter are still owned by the stack, so a partial failure
cannot leak a subprocess, and a server failing early no longer silently
discards the healthy ones after it in the list.
Tests state the bug directly: the old shape raises and never reaches the run
body, and stops entering at the first failure; the new one reaches it, keeps
every healthy connector including those after the failure, survives all
servers failing, and still unwinds what it entered.
Full suite: 7680 passed. The single failure
(`test_copilot_auth_model::test_ghe_host_is_passed_through`) is pre-existing
and reproduces identically on an unmodified checkout.
breedx
marked this pull request as draft
September 3, 2026 15:26
Contributor
Author
|
Closing this outdated implementation. Guarding the outer run-context loop does not adequately cover the actual MCP toolset entry and recovery lifecycle. This needs a separately scoped connector-lifecycle fix with end-to-end failure-containment, recovery and cleanup tests, rather than retaining this patch as the proposed solution. The underlying MCP resilience issue remains open; identity/prompt work is separate. |
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.
One MCP server that fails to start kills the entire run — no model call, no answer.
_run_agent_task_bodyenters everyagent_run_contextinto one sharedAsyncExitStack. A dead server raises from__aenter__, the stack unwinds, and_do_runon the next line never executes.Fix: enter each context in its own
try. A failure is reported by name; the run continues without that server's tools.Full suite: 7680 passed.
ruff check+format --checkclean.Why it looks handled but isn't
The
except* McpErrorhandler downstream prints:That reads like a warning attached to your answer. There is no answer — the coroutine falls off the end and returns
None. The message makes a total failure look like a partial one, which is why this took a while to spot.How it was found
A connector whose
uvxdependency resolve failed. The process existed, so every status flag said RUNNING and pre-run health checks passed cleanly. It only failed at context-entry time — by which point the all-or-nothing stack had already made it fatal.No amount of pre-run probing catches this; the failure happens after every check has run. That's why the fix has to be at the entry seam rather than in a health filter.
Design note
A connector is a capability, not a precondition. Losing one should cost its tools, not the answer — the agent still has its built-in file and shell tools, which is most of what it does.
Two details worth noting:
Tests
tests/agents/test_run_context_degrades.py:The single suite failure (
test_copilot_auth_model::test_ghe_host_is_passed_through) is pre-existing — verified to reproduce identically on an unmodified checkout ofmain.