DEV Community

Cover image for 9 Timeouts in Claude Code, Codex and Gemini CLI That Cut Long Test Runs Short
Gabriel Anhaia
Gabriel Anhaia

Posted on Edited on AI-assisted

9 Timeouts in Claude Code, Codex and Gemini CLI That Cut Long Test Runs Short


Your integration suite takes 14 minutes on a clean run. You ask Claude Code to run it. Two minutes in, the Bash tool stops waiting, and the result Claude gets back reads Command did not complete within its 120s timeout and was moved to the background (tools reference).

In your terminal, the suite keeps going in the background. Run the same job in CI with claude -p and a text prompt, and a moved command gets 10 more minutes before Claude Code stops it (same page). If Claude didn't ask for a longer timeout, that is 2 minutes in the foreground plus 10 in the background: 12 minutes for a 14-minute suite.

That is one of nine limits that Claude Code, Codex and Gemini CLI put on long work. Each item gives the setting's name, its default, where it goes and the symptom, from each vendor's docs as of November 6, 2026. I haven't measured any of them myself.

Claude Code's Bash tool: three clocks

1. BASH_DEFAULT_TIMEOUT_MS: 2 minutes

This is the timeout for a foreground Bash or PowerShell command when Claude passes no timeout of its own: 120000 ms (env vars). Claude can pass a longer timeout on any call. You can't set one per command yourself.

At the limit, Claude Code doesn't kill the command. It moves it to the background and says so, with the task ID and the output file. The exception: with CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 or in bare mode, a command that hits its timeout stops instead (tools reference). Set that variable to keep sessions tidy, and a long test run can stop at the 2-minute mark.

2. BASH_MAX_TIMEOUT_MS: 10 minutes

This is the ceiling on what Claude may request: 600000 ms by default. The effective ceiling is the larger of this value and BASH_DEFAULT_TIMEOUT_MS (env vars). So even when Claude knows your suite is slow and asks for 20 minutes, it gets 10.

3. The background time limit: 30 minutes, or 10 in claude -p

This applies only to unattended sessions (v2.1.285 or later): -p runs, Agent SDK apps, CI jobs and cloud sessions. A local terminal, desktop or VS Code session has no limit on background commands (tools reference).

A command Claude starts in the background gets 30 minutes, or the timeout Claude passes, up to 2 hours. A command moved there at its timeout gets 30 minutes from the move. In a -p run whose prompt you pass as text, both defaults are 10 minutes. At the limit, the notice reads Background command "<description>" was stopped after reaching its background time limit, and Claude can start it again with a longer timeout.

The same two variables fix all three. BASH_DEFAULT_TIMEOUT_MS above 1800000 replaces the 30-minute background default (above 600000 replaces the 10-minute one in a text-prompt -p run), and BASH_MAX_TIMEOUT_MS above 7200000 raises the 2-hour maximum. Neither can shorten a limit. A settings file's env block applies however claude was launched (env vars):

{
  "env": {
    "BASH_DEFAULT_TIMEOUT_MS": "1200000",
    "BASH_MAX_TIMEOUT_MS": "2400000"
  }
}
Enter fullscreen mode Exit fullscreen mode

That gives commands 20 minutes by default and lets Claude ask for up to 40, so the 14-minute suite finishes in the foreground. Put it in ~/.claude/settings.json, or in the repo's .claude/settings.json for the team, where most env values wait until each teammate trusts the folder (settings).

A long test run drawn as a ribbon of checkmarks, cut short by three stopwatches of different sizes
Three separate clocks sit on one Bash command in Claude Code.

Claude Code: MCP servers, hooks and the API

4. MCP_TIMEOUT: 30 seconds to start a server

The startup timeout for an MCP server is 30000 ms (env vars). It bounds each server's connect attempt. A server that downloads itself on first launch, such as an npx package, can miss that window, and its tools won't be in the session. The MCP docs give the shape of the fix:

MCP_TIMEOUT=60000 claude
Enter fullscreen mode Exit fullscreen mode

5. MCP tool calls: 60 seconds per request on remote servers

MCP_TOOL_TIMEOUT defaults to about 28 hours, so it rarely bites. Two quieter limits do. HTTP, SSE and claude.ai connector servers time out each request after 60 seconds unless MCP_TOOL_TIMEOUT or the server's own timeout is above 60000. And CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT aborts a call when the server sends nothing, not even progress, for 5 minutes on network servers or 30 on stdio (env vars).

The symptom is a remote MCP test runner or browser tool that works on short jobs and errors on long ones. Raise it per server in .mcp.json, in milliseconds:

{
  "mcpServers": {
    "test-runner": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "timeout": 1800000
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That timeout is a hard wall-clock limit that progress notifications don't extend, and it also sets the server's minimum idle window (MCP docs). A stdio server has no 60-second timer. There, the limit you'll hit is the 30-minute idle window, and because a per-server timeout is a hard wall clock, set it above your longest run or leave it out.

6. Hook timeout: 10 minutes, less on some events

Hook timeouts are in seconds. The defaults are 600 for command, http and mcp_tool hooks, 30 for prompt hooks and 60 for agent hooks. Claude Code lowers the 600 to 30 on UserPromptSubmit (hooks reference).

A hook that reaches its timeout is canceled and its output thrown away, so on most events it makes no decision. If your Stop hook runs the full suite as a gate and the suite takes 12 minutes, the gate says nothing. For that gate, raise the hook's timeout (in seconds) above the suite's run time, for example "timeout": 1200. On PreToolUse it's worse: a hook that times out doesn't block the tool call; the call goes on through the normal permission flow. To make a stall count as a no on a command or http PreToolUse hook (v2.1.295 or later), set "onFailure": "block". That key has no effect on Stop hooks (hooks reference).

7. API_TIMEOUT_MS: 10 minutes for response headers

This one guards the model call. It sets how long each API request attempt has to receive its response headers: 600000 ms by default. It doesn't limit how long a streaming response runs after that. Raise it on slow networks or behind a proxy; 0 removes it from v2.1.305 (env vars).

Nested clock faces, each one smaller and inside the last, around a terminal window
A per-request limit inside a per-call limit inside a session limit. Raise the outer one and the inner one can still fire.

Codex and Gemini CLI

8. Codex: startup_timeout_sec is 10, tool_timeout_sec is 60

Codex's MCP keys live in ~/.codex/config.toml, or in .codex/config.toml in a project you trust. A server gets 10 seconds to start and 60 seconds per tool call by default (config reference, MCP docs). That startup window is a third of Claude Code's.

[mcp_servers.test_runner]
command = "node"
args = ["mcp/test-runner.js"]
startup_timeout_sec = 30
tool_timeout_sec = 900
Enter fullscreen mode Exit fullscreen mode

For optional servers, Codex waits mcp_optional_startup_grace_ms (1000 ms by default) while it builds the first tool list; 0 makes it wait for each server's startup_timeout_sec. Set required = true on a server if a slow start should fail the launch. I couldn't find a key for shell command timeouts in the Codex config reference.

9. Gemini CLI: tools.shell.inactivityTimeout is 300 seconds

This is "the maximum time in seconds allowed without output from the shell command" (configuration). It counts silence, not total time. A test runner with a quiet reporter, or a build step that buffers its output until the end, can hit it long before it's done.

Units change inside the same file. The shell setting is in seconds. An MCP server's timeout is in milliseconds, with a default of 600000 (MCP server docs). A hook's timeout is also milliseconds, default 60000 (hooks). The shell and MCP settings go in ~/.gemini/settings.json or the project's .gemini/settings.json:

{
  "tools": {
    "shell": { "inactivityTimeout": 900 }
  },
  "mcpServers": {
    "test-runner": {
      "command": "node",
      "args": ["mcp/test-runner.js"],
      "timeout": 900000
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One status note from the same docs: for unpaid-tier and Google One users, Gemini CLI "was replaced by Antigravity CLI on June 18th, 2026." I haven't checked Antigravity CLI's own timeout keys, so they aren't on this list.

The cheat sheet

# Setting Agent Where Default
1 BASH_DEFAULT_TIMEOUT_MS Claude Code env / env block 120000 ms
2 BASH_MAX_TIMEOUT_MS Claude Code env / env block 600000 ms
3 Background limit Claude Code via 1 and 2 30 min (10 in -p)
4 MCP_TIMEOUT Claude Code env 30000 ms
5 Per-request MCP limit Claude Code .mcp.json timeout 60 s (remote)
6 Hook timeout Claude Code settings.json hooks 600 s (command)
7 API_TIMEOUT_MS Claude Code env / env block 600000 ms
8 startup_timeout_sec, tool_timeout_sec Codex config.toml 10 s, 60 s
9 tools.shell.inactivityTimeout Gemini CLI settings.json 300 s

Watch the units. Claude Code hooks take seconds and its env variables take milliseconds. Codex and Gemini CLI each mix both.

Telling a finished run from a cut-off one in Hermes

I build Hermes IDE, so weigh this with that in mind. None of the limits above live in Hermes. It opens each agent in a real terminal by default, running the agent's own interface (README), so you set these limits in each agent's own config, and the agent's timeout notices appear in its own terminal.

Hermes adds one view across agents. Every session shows one status: working, needs approval, asked you, done, error or idle, and Hermes says when a status is a guess. A notification fires when an agent is blocked on you or done. On a long suite, that tells you which session has stopped and which is still working, without opening every terminal.

Two agent sessions side by side in Hermes IDE, Codex and Claude Code, each in its own worktree, with the sessions sidebar showing status, context use, cost and branch
Codex on orders-types and Claude Code on orders-api, each in its own hermes/ worktree, with status, context use, cost and branch for each session in the sidebar.

"Done" means the agent finished its turn, not that your suite passed. A turn can end right after a timeout notice. Since 2.3.1, the "Agent replies" strip under each session has one chip per reply with the lines it changed, and a click shows that reply's changes. Read the last one before you trust a green-looking summary.

Your own checks can also decide when a task is done: the worktree recipe docs show done_when = ["npm test"] in .hermes/worktree.toml. I haven't timed a 14-minute suite through it. On macOS and Linux, agents keep working while Hermes quits, updates or crashes.

Hermes is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Nous Research, Anthropic, OpenAI or Google. If a limit caught you that I didn't list, tell me in the comments.


If this was useful

Most of these are limits stacked on limits, like a per-request timeout sitting under a per-call one. That is ordinary timeout design for any distributed system, and the System Design Pocket Guide: Fundamentals covers it, along with the other core building blocks, in a short read. And if you run more than one of these agents a day, Hermes IDE puts their status in one place.

System Design Pocket Guide: Fundamentals — Core Building Blocks for Scalable Systems

Top comments (1)

Collapse
 
steven_mock_afbb801174fc3 profile image
Steven Mock •

This is the reference I wish I'd had the first time a long suite got cut off at two minutes and I assumed the tests were flaky. The part most people will miss is that the background limit is driven by those same two env vars — raising one without the other just moves the cutoff. The units warning is worth pinning too. Seconds in one place and milliseconds in another is exactly how these get misconfigured.