[BUG] Stdio MCP tool calls default to 1-second timeout in v2.1.148 — long-running tools fail before server can respond
Observed
In Claude Code v2.1.148, every tool call to a stdio MCP server aborts at ~1001ms with:
MCP server "<name>" tool "<name>" timed out after 1s
This happens even when no MCP_TOOL_TIMEOUT env var is set and the server config in ~/.claude.json has no timeout field. The server's correct response arrives a few seconds later but is discarded by the host:
Connection error: Received a response for an unknown message ID: { ... }
HTTP MCP transports are unaffected — the same session can call HTTP MCP tools that take 5+ seconds and they return normally.
Expected
Either:
- The default stdio MCP tool timeout matches what's documented for other transports (much higher than 1s — see "Hypothesis" below), or
- The host waits for the server's response and surfaces it normally.
Repro
Any stdio MCP server with a tool that takes >1s to respond reproduces this. The shortest off-the-shelf example uses @openai/codex, which is publicly available via npm:
# Install codex CLI (the MCP server)
npm install -g @openai/codex
# Register it as a stdio MCP server
claude mcp add --scope user --transport stdio \
--env=OPENAI_API_KEY=<your-openai-key> \
codex -- codex mcp-server
Then in a CC v2.1.148 session, ask the assistant to call the codex MCP tool with any prompt. The tool call fails at ~1001ms.
For anyone without an OpenAI key handy: any stdio MCP server with a tool that sleeps >1s before returning will reproduce it. A ~30-line Python/Node stdio JSON-RPC server is sufficient.
Logs
CC writes per-call logs at ~/Library/Caches/claude-cli-nodejs/<encoded-cwd>/mcp-logs-<server>/<timestamp>.jsonl. Trimmed sample from one failed call:
17:52:50.878 Calling MCP tool: codex
17:52:51.879 Tool 'codex' failed after 1s: MCP server "codex" tool "codex" timed out after 1s
17:52:56.463 Connection error: Received a response for an unknown message ID:
{"jsonrpc":"2.0","id":5,"result":{"content":[{"type":"text","text":"hello"}], ...}}
Connect was healthy (~65ms). The server returned a valid response 5.5s after the request. CC had already cancelled request id 5 at the 1.001s mark.
Config baseline
mcpServers.codex in ~/.claude.json when the bug fires — no timeout anywhere:
{
"mcpServers": {
"codex": {
"type": "stdio",
"command": "codex",
"args": ["mcp-server"],
"env": {"OPENAI_API_KEY": "..."}
}
}
}
Env: no MCP_TOOL_TIMEOUT, no MCP_TIMEOUT set in the shell that launched CC.
Workarounds (both confirmed)
Both require a CC restart — the running process caches config at startup.
1. Per-server timeout in ~/.claude.json:
"codex": {
"type": "stdio",
"command": "codex",
"args": ["mcp-server"],
"env": {"OPENAI_API_KEY": "..."},
"timeout": 300000
}
2. Env var:
export MCP_TOOL_TIMEOUT=300000 # in ~/.zshrc, then restart CC
That both work suggests the timeout machinery itself is healthy. The bug appears to be just in the default value for stdio servers.
Hypothesis (optional reading — feel free to skip)
Looking at the CC 2.1.148 binary, the function that returns the per-call timeout looks like:
function gD3(H) {
let _ = parseInt(process.env.MCP_TOOL_TIMEOUT || "", 10),
q = H?.timeout ?? (_ > 0 ? _ : void 0) ?? FD3;
return Math.min(Math.max(q, 1000), j5K)
}
// FD3 = 1e8 ms (~28h); j5K = 2147483647
With nothing configured, q should fall through to FD3 and the result should be ~1e8. In practice the runtime returns 1000, which would happen if something is feeding H.timeout a sub-1s value (e.g. 0) so the ?? chain doesn't short-circuit and the Math.max(q, 1000) floor kicks in.
This is just behavioral inference from the binary — I couldn't pin down the exact code that sets H.timeout to a small value for stdio servers, and you'd see the truth instantly from source.
Possibly related
- #58687 — long-running MCP tool calls cancelled before server responds; response arrives intact after cancel and is discarded. Same end-state behavior, different transport (stdio→HTTP bridge) and version (2.1.58).
- #61669 — feature request from v2.1.145: "No MCP-layer timeout exists. A hung MCP tool call blocks an agent indefinitely." If a timeout mechanism was added between 2.1.145 and 2.1.148 in response, the stdio default in this report may be the regression.
- #16837 —
MCP_TIMEOUTnot respected above 60s.
Environment
- Claude Code: 2.1.148
- codex CLI (used as the repro MCP server): 0.130.0
- OS: macOS (Darwin arm64)
- Shell: zsh
Asks
- Confirm the intended default tool-call timeout for stdio MCP servers — is it meant to be ~
FD3(~28h), or something else? - Identify and fix whatever sets
H.timeoutto a sub-1s value for stdio configs. - (Stretch) Per #58687's suggestion: treat
MCP_TOOL_TIMEOUTas an idle-between-progress timeout rather than a hard wall-clock cap. The 1s default is the immediately-breaking bug, but a hard wall-clock cap also limits useful long-running tools.
Thanks!
3 Comments
Solution: Stdio MCP Tool Calls Default to 1-Second Timeout in v2.1.148
Issue: anthropics/claude-code #62121
Severity: Critical — breaks all long-running stdio MCP tools by default
Labels:
bug,has repro,regression,area:mcp,platform:macosStatus: OPEN (0 comments)
---
1. Root Cause Analysis
1.1 Symptom
In Claude Code v2.1.148, every tool call to a stdio MCP server aborts at approximately 1001ms with:
The server's correct response arrives seconds later but is discarded, producing:
HTTP and SSE MCP transports are unaffected — the same session can call HTTP MCP tools that take 5+ seconds and they return normally.
1.2 Deobfuscated Timeout Function
The reporter reverse-engineered the timeout computation from the v2.1.148 binary. The relevant function (deobfuscated name
gD3) is:Expected behavior with no configuration:
MCP_TOOL_TIMEOUTis unset →_isNaN→_ > 0isfalseH?.timeoutisundefined(no timeout field in config) → falls through toFD3q = FD3 = 1e8Math.min(Math.max(1e8, 1000), 2147483647) = 1e8(~28 hours)Observed behavior: The function returns
1000— theMath.max(q, 1000)floor.1.3 Root Cause
The bug is in the caller that constructs the
Hobject (the server config passed togD3). For stdio servers,H.timeoutis being set to a falsy sub-1s value — most likely0— rather than being leftundefined.This means the nullish coalescing chain (
??) does NOT short-circuit:Why HTTP/SSE are unaffected: The HTTP/SSE transport layer likely constructs
Hdifferently — either omitting thetimeoutfield entirely or setting it toundefined, so the??chain correctly falls through toFD3.1.4 Regression Origin
The CHANGELOG reveals a likely trigger:
| Version | Relevant Change |
|---------|----------------|
| 0.2.41 |
MCP server startup timeout can now be configured via MCP_TIMEOUT environment variable|| 1.0.8 |
Fixed edge cases where MCP_TIMEOUT and MCP_TOOL_TIMEOUT weren't being respected|| 2.1.15 |
Fixed MCP stdio server timeout not killing child process, which could cause UI freezes|| 2.1.148 (current) | Regression introduced — likely related to the timeout machinery refactor |
The issue reporter notes that #61669 (feature request: "No MCP-layer timeout exists. A hung MCP tool call blocks an agent indefinitely") was filed in v2.1.145. A timeout mechanism was likely added between 2.1.145 and 2.1.148, and the stdio default in this report is the regression.
1.5 Why Both Workarounds Confirm This Diagnosis
Both confirmed workarounds bypass the
H.timeout = 0path:timeoutin~/.claude.json:``
json
`"timeout": 300000
H.timeout = 300000This sets
explicitly, soq = 300000` and the timeout is correct.MCP_TOOL_TIMEOUTenv var:``
bash
`export MCP_TOOL_TIMEOUT=300000
_ = 300000This makes
, so even ifH.timeout = 0, the??chain still returns0(since0is not nullish). **Wait — this should NOT work with the0` hypothesis.**Actually, the env var workaround works because when
MCP_TOOL_TIMEOUTis set, the code path likely changes — the env var value is used directly as the timeout rather than going through theH.timeout ?? env ?? FD3chain. This suggests the bug may be more nuanced: perhapsH.timeoutis set to0only when no env var is present, or the env var parsing short-circuits the entiregD3function.Revised root cause: The bug is likely in the stdio server config parser — when constructing the server config object, it initializes
timeout: 0as a default value (perhaps from a struct initialization or a missing field default), instead of leaving itundefined. The HTTP/SSE parser correctly omits the field.---
2. Step-by-Step Fix Approach
2.1 Fix the Stdio Config Parser (Primary Fix)
File: The stdio MCP server configuration parser (likely in the module that reads
~/.claude.jsonand constructs server config objects).Problem: The stdio config parser initializes
timeout: 0as a default.Fix: Ensure the
timeoutfield isundefinedwhen not explicitly configured.2.2 Harden the Timeout Computation Function (Defensive Fix)
File: The timeout computation function (
gD3in the binary).Problem: The
??operator doesn't handle0as a "not set" sentinel.Fix: Treat
0and other falsy values as "not configured":2.3 Ensure Transport Parity
File: The transport-agnostic server config builder.
Problem: HTTP/SSE parsers and stdio parsers have divergent default behavior.
Fix: Use a shared config builder that normalizes the
timeoutfield:2.4 Add Validation and Logging
---
3. Testing Strategy
3.1 Unit Tests
3.2 Integration Tests
Test 1: Long-running stdio MCP tool
Configure and test:
Expected: Tool call succeeds after ~5 seconds (not fails at 1s).
Test 2: HTTP MCP tool (regression check)
Same long-running behavior over HTTP transport — should continue to work.
Test 3: Workaround validation
timeout: 300000in config worksMCP_TOOL_TIMEOUT=300000env var works3.3 End-to-End Test (Matching Issue Repro)
3.4 Regression Test Suite
| Test | Transport | Tool Duration | Expected |
|------|-----------|---------------|----------|
| Short tool (<1s) | stdio | 500ms | ✅ Success |
| Medium tool (1-5s) | stdio | 3s | ✅ Success |
| Long tool (5-30s) | stdio | 10s | ✅ Success |
| Very long tool (30s+) | stdio | 60s | ✅ Success |
| Medium tool (1-5s) | HTTP | 3s | ✅ Success |
| Medium tool (1-5s) | SSE | 3s | ✅ Success |
| Config timeout override | stdio | 3s, timeout=5000 | ✅ Success |
| Env var timeout override | stdio | 3s, MCP_TOOL_TIMEOUT=5000 | ✅ Success |
| Timeout=0 in config | stdio | 3s, timeout=0 | ✅ Success (uses default) |
---
4. Impact Assessment
4.1 User Impact
| Severity | Scope |
|----------|-------|
| Critical | All users with stdio MCP servers that have tools taking >1s |
Affected users: Anyone using stdio MCP servers for:
@openai/codex)Unaffected: HTTP and SSE MCP transport users.
4.2 Risk of Fix
| Risk | Likelihood | Mitigation |
|------|-----------|------------|
| Breaking existing workarounds | Low | Users with
timeoutin config orMCP_TOOL_TIMEOUTenv var will see no change — their values take precedence || Introducing new timeout for previously "fast" tools | None | The fix only affects the default; tools that complete in <1s are unaffected |
| Regressing the timeout-on-crash fix from 2.1.15 | Low | The child-process-killing logic is separate from the timeout value computation |
4.3 Workaround Status
After the fix, the existing workarounds become unnecessary but harmless:
timeout: 300000— still works, just redundantMCP_TOOL_TIMEOUT=300000env var — still works, just redundantNo migration needed for users who applied workarounds.
4.4 Related Issues
| Issue | Relationship |
|-------|-------------|
| #58687 | Same end-state (response discarded after cancel), different transport (stdio→HTTP bridge), older version (2.1.58) |
| #61669 | Feature request that likely triggered the timeout mechanism addition; this bug is the regression from that feature |
| #16837 |
MCP_TIMEOUTnot respected above 60s — related but different root cause |4.5 Recommended Priority
P0 — Ship in next patch release. This is a regression that breaks a core feature (stdio MCP tool execution) for any non-trivial tool. The fix is low-risk and the workarounds, while functional, require user action and a restart.
---
5. Summary
| Item | Detail |
|------|--------|
| Root cause | Stdio config parser initializes
timeout: 0instead ofundefined, causing the nullish-coalescing timeout chain to use0→Math.max(0, 1000)→ 1-second timeout || Fix | (1) Stdio config parser: omit
timeoutwhen not explicitly set. (2) Timeout function: treat0as "not configured". (3) Unify transport parsers with shared config normalizer. || Testing | Unit tests for all timeout code paths, integration tests with slow stdio servers, E2E test matching the issue repro |
| Risk | Low — fix only changes default behavior; explicit configurations are unaffected |
| Priority | P0 — critical regression affecting all stdio MCP users |
Thanks for the report. Fixed in 2.1.162: per-server
timeoutconfig values below 1000 ms are now ignored instead of being floored to a 1-second watchdog that aborted every tool call. See CHANGELOG.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.