[BUG] Bash tool hides errors and hangs from non-terminating commands when output is piped through `tail`/`head` (or any EOF-buffering filter)

Resolved 💬 1 comment Opened Jun 1, 2026 by makerSimon Closed Jul 5, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

The Bash tool's output capture pattern silently drops stdout/stderr from any command whose downstream filter (tail -N, head -N, sort, etc.) emits only on EOF. When the upstream command hangs, crashes mid-stream without closing its pipe, or simply runs longer than the agent expects, the agent sees an empty result — indistinguishable from a quiet success. Errors that the user could see directly in a terminal become invisible to the model.

Reproduction

Any Claude Code session where the agent reaches for the idiomatic pattern:

RAILS_ENV=test bin/rails test 2>&1 | tail -40
npm run build 2>&1 | tail -50
pytest -q 2>&1 | tail -30

If the upstream process:

  • writes output and then hangs (deadlock, waiting on stdin, server starts and never exits),
  • crashes with an error that prints to stderr but doesn't close all open file descriptors promptly, or
  • is killed by the harness timeout before it terminates cleanly,

then tail -N is still blocked reading its input, has emitted nothing, and the Bash tool's response is empty. The agent then has no signal to act on — it concludes "no output, must be OK" and proceeds, or it concludes "I should try again" and re-runs the same broken command.

We hit this concretely in a Rails project: RAILS_ENV=test bin/rails test 2>&1 | tail -40 hid a Minitest.run_one_method failure for several minutes. The user had to manually intervene and tell the agent the test suite was broken — there was no way for the agent to discover it on its own.

Why it happens

tail -N (without -f) is fundamentally an EOF-driven filter. It must consume the entire stream before it knows which N lines are last. The pipe doesn't close until the upstream process terminates. So for any non-terminating upstream, tail is a black hole. The agent's 2>&1 | tail -N habit is a coping mechanism for context-window limits, but the cost is silently dropping the very output the agent needs to debug failures.

The model's own training reinforces the pattern (it's the standard Unix-y way to "show me the last bit of a long output"), so it will keep reaching for it unless the harness itself makes the pattern safe — or makes a better pattern equally cheap.

Impact

This affects every Claude Code user who runs builds, tests, servers, migrations, or any potentially-hanging command. The current model habit of cmd 2>&1 | tail -N is exactly the wrong tool for the cases where the user most needs the agent to detect and report failures. Users have to babysit the agent and surface the error manually — which is the failure mode this tool was built to prevent.

What Should Happen?

Proposed fixes

In rough order of effort vs. payoff:

  1. Always tee to a durable file. Have the Bash tool capture full stdout/stderr to $CLAUDE_JOB_DIR/<id>.log (or equivalent) and present both the tail-truncated text and a path the agent can Read for the rest. Then even if the agent reaches for | tail -N, the raw output exists and is inspectable. This is the smallest change with the biggest payoff.
  1. Stream output incrementally rather than presenting a single EOF-delimited block. A PTY-style capture or periodic flushing of partial stdout into the tool result would let the agent see "the command produced 300 lines and then went quiet" vs. "the command produced 0 lines."
  1. Update the agent's system prompt / tool guidance to actively discourage | tail -N in the Bash tool's docstring, and to recommend run_in_background / Monitor / "tee to file then Read" as defaults. The current Bash docstring warns against cat/sed/echo but not against the much more dangerous | tail -N pattern.
  1. Add a max_output_lines parameter to the Bash tool that does truncation server-side, after the command terminates, instead of forcing the agent to build a fragile pipeline. The harness knows when to truncate without losing the underlying stream.

Any of (1)–(4) would be a material improvement; (1) is the smallest, most defensible change.

Error Messages/Logs

Steps to Reproduce

Reproduction

Any Claude Code session where the agent reaches for the idiomatic pattern:

RAILS_ENV=test bin/rails test 2>&1 | tail -40
npm run build 2>&1 | tail -50
pytest -q 2>&1 | tail -30

If the upstream process:

  • writes output and then hangs (deadlock, waiting on stdin, server starts and never exits),
  • crashes with an error that prints to stderr but doesn't close all open file descriptors promptly, or
  • is killed by the harness timeout before it terminates cleanly,

then tail -N is still blocked reading its input, has emitted nothing, and the Bash tool's response is empty. The agent then has no signal to act on — it concludes "no output, must be OK" and proceeds, or it concludes "I should try again" and re-runs the same broken command.

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.150 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗