[Bug] Claude still wraps commands in unavailable `timeout`/`gtimeout` on macOS instead of using built-in bash-timeout (re-file of locked #3361 / #10600)
Re-filing #3361 and #10600 — both were auto-closed (NOT_PLANNED by stale-bot, duplicate respectively) and locked, with no fix and no maintainer response. The bot in #3361 explicitly says: "If you're experiencing a similar issue, please open a new issue." This is that new issue.
Pinging the original reporters so context isn't lost: @lukehutch (#3361), @Geenz (#10600).
---
Summary
On macOS (where GNU timeout/gtimeout are not installed by default), Claude Code repeatedly wraps shell commands in timeout N <cmd> / gtimeout N <cmd>. The command fails with command not found: timeout, the model burns tokens reacting to it, and in a non-trivial fraction of cases hallucinates success on the empty output. Claude Code has a built-in bash-timeout (BASH_DEFAULT_TIMEOUT_MS / BASH_MAX_TIMEOUT_MS), but the model does not reach for it — it treats the external timeout utility as the only lever.
Environment
- Claude Code
2.1.214 Darwin 25.5.0 arm64(macOS),zshtimeout/gtimeoutnot present;coreutilsnot installed via brew
Reproduction
Any long-running or network-shaped task tends to trigger it. The model emits commands like:
timeout 300 <long-running-command> 2>&1 | tail -30
Result:
(eval):1: command not found: timeout
Evidence from real transcripts
Mining Claude Code session transcripts to quantify this, across the corpus:
- 71 Bash invocations were wrapped in an external
timeout N/gtimeout Nprefix. - The error
command not found: timeoutappears ~441 times in tool results — the real scale of the harm. - 104
--timeout=/--timeout Ninvocations (legitimate pytest/tool flags) were correctly used — so the model knows the concept of a timeout; the failure is specifically the external command.
The harness itself signals the built-in timeout to the model (Command timed out after 2m 0s appears in 68 session files), yet the model's reaction is to prepend an external timeout to the next command rather than reason about BASH_MAX_TIMEOUT_MS.
Why it happens (classified from 31 contextual cases)
The model does not distinguish between Claude Code's built-in bash-timeout and the GNU timeout utility. In its world:
- the 2-minute harness limit is a fixed property of the environment;
timeout Nis a separate utility to "give a command less time" or "kill a hung process".
The link "I want >2 min → raise BASH_MAX_TIMEOUT_MS" is never made — even though that env var is surfaced in the Claude Code changelog the model sees at session start. The model's justifications fall into three buckets:
| Intent | Share | Example (verbatim, anonymized) |
|---|---|---|
| (a) Fears a hang (pytest deadlock / input() / network CLI) | ~55% | "…the process seems to have hung. I'll run it in the foreground with a timeout to see the real result." |
| (b) Knows the 2-min harness ceiling, wants more | ~13% | "The command hung and exceeded the 2-minute timeout." → next command wrapped in timeout 20 |
| (c) Ritual / no stated reason | ~32% | wraps long-running pipeline commands in timeout 300 by habit |
Reaction to command not found: timeout (across cases)
- ~50% — model notices and substitutes (re-run without prefix, switch to
pytest --timeout=N, or a different tool). - ~20% — model ignores the error and reasons about the code as if the command had run; observed a hallucination "all 22 tests are green" on a command that never executed. This is the most harmful mode.
- Token-burn diagnostic loops are rare because the error is unambiguous — the harm is mostly the silent-success hallucinations and the friction.
Suggested fix (any of)
- Treat
timeout/gtimeoutas an unavailable command in the model's environment knowledge on macOS, and either (a) auto-strip the prefix and rely on the built-in timeout, or (b) surface a hint to useBASH_DEFAULT_TIMEOUT_MS/BASH_MAX_TIMEOUT_MSwhen the model's stated goal is "give the command more than 2 minutes". - At minimum, when a Bash command returns
command not found: timeout, do not let the model interpret empty output as success.
Related
- #3361 (locked) — original; closed
NOT_PLANNEDby stale-bot. - #10600 (locked) — closed as duplicate of #3361.
- #76082 (open) — separate but related:
normalizeToolInputsilently corrupts bash commands, relevant if a hook-based rewrite approach is considered.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗