Background-task stdout leaks into foreground Bash tool output via a shared controlling PTY (Claude Desktop, macOS)

Resolved 💬 2 comments Opened Jun 24, 2026 by xueyongcheng Closed Jun 27, 2026

Preflight Checklist

  • [x] I have searched existing issues and found no duplicate
  • [x] This is a single bug report
  • [x] I am using the latest version

What's Wrong?

In Claude Desktop (macOS app), when a session spawns a background task (a run_in_background Bash call, or a background subagent), the task process is not isolated from the foreground Bash tool's controlling PTY. The background task's stdout is written both to its own tasks/*.output file and leaks back onto the foreground pseudo-terminal (PTY). As a result, output from the background task intermittently bleeds into the results of unrelated foreground Bash tool calls in the same session, appearing at positions unrelated to the command that was actually run.

Observed: a string being processed by a background task repeatedly appeared inside the output of grep -oE, git push, and ps — commands that are structurally incapable of producing that text.

What Should Happen?

A foreground Bash tool call should return only what its command actually wrote to stdout/stderr. Background tasks should write only to their own tasks/*.output sink and never contaminate the foreground PTY.

Error Messages / Logs

# 1) The foreground Bash tool's stdio is a tty, not a pipe:
$ lsof -p <shell-pid> -d 0,1,2
0u CHR 16,5 /dev/ttysNNN
1u CHR 16,5 /dev/ttysNNN
2u CHR 16,5 /dev/ttysNNN

# 2) Only two processes hold that tty — no third party:
$ lsof /dev/ttysNNN
Claude  <pid>  ... 105u CHR 16,5 /dev/ttysNNN          # the Claude.app process
zsh     <pid>  ... 0u,1u,2u,10u CHR 16,5 /dev/ttysNNN  # the Bash-tool shell
# -> rules out the intuitive "another session/daemon writes to my terminal"
#    theory (those live on different ttys)

# 3) Shell ancestry confirms it is the desktop app:
zsh <pid>  <-  /Applications/Claude.app/Contents/MacOS/Claude (<pid>)

# 4) The injected text originated from a background task's output file
#    in THIS session:
/tmp/claude-<uid>/<project>/<session>/tasks/<taskid>.output
# confirmed to belong to the current session via the
# tasks/<id>.output -> subagents/agent-<id>.jsonl symlink
Evidence caveat (stated honestly): the background task had already exited when fds were captured, so the one frame NOT directly captured is the live snapshot of that task process holding both its .output file and /dev/ttysNNN simultaneously. The mechanism below is the strongest inference from: foreground stdio is a tty + injected text sourced from a background task's output sink + no third-party holder of the tty + the leak appears only while the task is emitting.

Steps to Reproduce

Timing-dependent — only visible while a background task is actively emitting stdout.
  1. macOS, Claude Desktop app.
  2. In a session, start a long-running background task that continuously emits stdout (a Bash tool call with run_in_background, or a background subagent doing heavy work).
  3. While that task is still running and printing, issue ordinary foreground Bash tool calls (git push, grep, ps, even echo).
  4. Observe: lines of the background task's output are interleaved into the foreground command's result, at positions unrelated to that command.

Environment

  • Claude Model: Opus (claude-opus-4-8, 1M context)
  • Is this a regression? Unsure / possibly present since inception (no known-good baseline)
  • Last Working Version: Unknown
  • Claude Code Version: 2.1.187 (Claude Code); Claude Desktop app version 1.14271.0
  • Platform: Anthropic API (official Opus this session; the bug is independent of the API provider — it is an app-layer PTY issue)
  • Operating System: macOS 26.5.1 (build 25F80), Darwin 25.5.0, arm64 (Apple Silicon)
  • Terminal / Shell: Claude Desktop app's built-in shell (/bin/zsh -l via a PTY) — not an external terminal. This is the key point: the bug is in the PTY the desktop app allocates for the Bash tool.

Additional Information (root-cause inference + suggested fix + workaround)

Root-cause inference: Claude Desktop allocates a PTY for the session's foreground Bash tool shell. When the session concurrently runs a background task, the task process inherits/shares the foreground PTY's file descriptors instead of being fully detached with stdout/stderr redirected exclusively to its own tasks/*.output sink. Bytes the task writes therefore reach the PTY the app reads to assemble the foreground tool result.

Suggested fix:

  • When spawning background tasks (run_in_background / background subagents), fully isolate them from the foreground PTY: redirect stdout/stderr to tasks/*.output only (open the sink and dup2 onto fds 1/2, and close inherited PTY fds), and/or setsid() into a new session/pgrp, so no bytes can reach the interactive PTY.
  • Have each foreground Bash tool invocation capture stdout via its own dedicated pipe, rather than reading a shared PTY that other in-session processes can write into.

Severity assessment: Medium–High. No data corruption (git commits, file writes all complete correctly), but the observability layer is unreliable — tool results returned to (and recorded for) the model contain text the command never produced. During this investigation it demonstrably misled the model into several consecutive wrong diagnoses; in agentic/automated contexts, contaminated tool output fed back to the model can drive incorrect decisions.

Workaround: (1) Avoid issuing foreground Bash commands while a background task is actively emitting; (2) when a background task runs concurrently, treat foreground tool output as potentially contaminated and cross-check anything critical.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗