fix(mcp,doctor): serve MCP under a pty; explicit Codex args = ["mcp"] - #1616
Merged
Conversation
Maintainer follow-up on #1597, applied to the contributor's branch so the work stays theirs. Two changes, both from the review. 1. `codex_toml_upserts_existing_section` still asserted `args = []` and took down Test (ubuntu-latest), Test (windows-latest) and Coverage. It now asserts the writer's new output, and a companion test pins the case the writer deliberately leaves alone: `args = ["mcp", "--verbose"]` survives verbatim rather than being flattened to `["mcp"]`. 2. `check_codex_toml` required the `mcp` argument, which made every existing installation drift. `args = []` and an absent `args` both start the stdio server — `cli/dispatch/server.rs` matches `None | Some("mcp")` — so the doctor would have gone red for users whose config works, and `--fix` would have rewritten a functional file into a semantically identical one. That is the #1596 failure mode. The check now accepts absent, empty, and `mcp`-carrying `args`, and flags only an `args` array that replaces the entry point with something else (`["dashboard"]`). The writer side keeps the PR's behaviour unchanged: new installs are written explicitly as `args = ["mcp"]`, matching the Copilot writer. New installs are explicit, existing installs stay green, nobody's file is rewritten for nothing. The `args = ["mcp"]` default is worth having on its own merits, and it is also a real workaround for #1595: a client that spawns the server under a pty gets the quickstart instead of a server on a bare invocation. The root cause of that is fixed separately. Verification: cargo test --lib (10546 passed, 0 failed), cargo clippy --lib --all-features -D warnings clean, cargo fmt clean. Co-Authored-By: reysilvaa <reysilvaa@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#1595 reported that upgrading 3.9.18 → 3.9.20 broke the MCP connection unless `args = ["mcp"]` was added to the Codex config. I reviewed that report, probed a shipped binary, and told the reporter it did not reproduce. My probe piped stdin. Their environment does not — Devin, containerized agent runners, and anything driving the child through `script`/`expect` hand the process a pty. stdin = pipe -> "lean-ctx 3.10.1 MCP server starting" works stdin = pty, bare -> quickstart text, process exits broken stdin = pty + mcp -> server starts works `run()` treated a terminal on stdin as proof that a human was there, printed the quickstart and returned. For every client that allocates a pty the server therefore never came up, and nothing in the client's log explained why: the child exited 0 after writing help text to stdout. A TTY says a human *may* be watching. It never says a client is absent, and there is no fd-level signal that distinguishes the two. So the quickstart stops being a decision about whether to serve: it goes to stderr — which MCP clients log rather than parse — and the server starts either way. The human sees the explanation and presses Ctrl-C; the client completes its handshake on stdout. The text now says the process is serving and how to stop it, instead of claiming it "is waiting silently" before exiting. `mcp_pty_startup_1595` allocates a real pty with `openpty` (echo off), spawns the binary with no subcommand, and requires an `initialize` response. Restoring the `return` makes it fail, so it pins the behaviour rather than the wording. Verification: cargo test --lib (10538 passed, 0 failed), the new pty test green and red without the fix, cargo clippy --lib --all-features -D warnings clean, cargo fmt clean. Fixes #1595 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
Thanks for review my pull request, and i kindly help to keep contributing in this project |
This was referenced Aug 30, 2026
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Supersedes #1597 by @reysilvaa, whose commit is preserved here with their authorship — I could not push to their fork (403), so the branch lives in this repo instead. Also fixes the root cause their report uncovered.
What @reysilvaa got right
args = ["mcp"]really is the correct default, and their writer changes land unchanged:write_codex_config,upsert_codex_tomlandensure_codex_mcp_servernow emit it, matching what the Copilot writer already did.What I changed in their patch
1. The red test.
codex_toml_upserts_existing_sectionstill assertedargs = []and was taking down Test (ubuntu-latest), Test (windows-latest) and Coverage. Fixed, plus a companion test for the behaviour the writer deliberately preserves:args = ["mcp", "--verbose"]survives verbatim instead of being flattened.2.
args = []is not drift. Theircheck_codex_tomlrequired themcpargument, which would have turned every existing Codex installation red. Bothargs = []and an absentargsstart the stdio server (cli/dispatch/server.rsmatchesNone | Some("mcp")), so the doctor would have reported a working config as broken and--fixwould have rewritten it into a semantically identical file — the #1596 failure mode, hitting far more users than the change helps. The check now accepts absent / empty /mcp-carrying args and flags only an args array that replaces the entry point (["dashboard"]).The root cause — and my earlier review was wrong about it
I told @reysilvaa and the reporter of #1595 that bare invocation still serves MCP, and showed a probe to prove it. That probe was wrong: I piped stdin. The reporter's environment gives the process a PTY, and
cli/dispatch/mod.rstreats a TTY on stdin as "a human is here", prints the quickstart and exits.Reproduced against the shipped 3.10.1 binary:
So the report was accurate and
args = ["mcp"]was a real workaround, not a cosmetic one. Devin, containerized agent runners, and anything driving the child throughscript/expectallocate a PTY; every one of them silently failed to connect, with nothing in the logs to explain it.The fix: a TTY tells us a human may be watching — it never tells us a client is absent. The quickstart now goes to stderr (which MCP clients log rather than parse) and the server starts either way. The human reads what is happening and presses Ctrl-C; the client completes its handshake on stdout. The quickstart text says so instead of claiming the process "is waiting silently".
rust/tests/suite/mcp_pty_startup_1595.rsallocates a real pty withopenpty(echo off), spawns the binary with no subcommand, and requires aninitializeresponse. It fails against the previous behaviour.Verification
cargo test --lib→ 10546 passed, 0 failedcargo clippy --lib --all-features -- -D warnings→ cleancargo fmt --check→ cleanFixes #1595
Closes #1597
Co-authored-by: reysilvaa reynaldsilva123@gmail.com
🤖 Generated with Claude Code