claude-code - 💡(How to fix) Fix [BUG] Claude Code suspends with SIGTTIN/SIGTTOU on launch (macOS 26)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

zsh: error on TTY read: Input/output error

Error Messages/Logs

the orphaned-process-group exception). When the host shell does not call

Root Cause

Suspected root cause: The claude binary appears to call tcsetattr() during startup without first ensuring it is the controlling TTY's foreground process group, and without ignoring SIGTTOU. POSIX requires that tcsetattr() from a process not in the foreground process group raises SIGTTOU on the calling process (regardless of the orphaned-process-group exception). When the host shell does not call tcsetpgrp() to hand TTY ownership to claude before exec, claude is stopped immediately.

Fix Action

Fix / Workaround

sample(1) stack of the stopped process:

1724 Thread … DispatchQueue_1: com.apple.main-thread + ... (claude internals) ... + 1724 tcsetattr (in libsystem_c.dylib) + 172 + 1724 ioctl (in libsystem_kernel.dylib) + 36 + 1724 __ioctl (in libsystem_kernel.dylib) + 8

tcsetattr from a non-foreground process group → SIGTTOU → stopped.


Workarounds tried on user side (all unsatisfactory):
  - `status job-control full` in fish — does not consistently fix
  - `script -q /dev/null claude` (BSD script PTY wrapper) — claude starts but
     stdin forwarding is unreliable; claude exits soon after
  - python3 `pty.spawn` — works for input but PTY size defaults to 80×24 and
     SIGWINCH is not forwarded by stdlib pty.spawn

Code Example

$ claude
   ▐▛███▜▌   Claude Code v2.1.129
  ▝▜█████▛▘  Opus 4.7 with high effort · API Usage Billing
    ▘▘ ▝▝    /Users/<me>

  ❯ fish: Job 1, 'claude' has stopped

  # In zsh:
  $ claude
  ... (banner) ...
  zsh: suspended (tty input)  claude
  zsh: error on TTY read: Input/output error
  [Process completed]

  # Diagnostic snapshot of the stopped process:
  $ ps -o pid,stat,ppid,pgid,tpgid,command -p <claude-pid>
    PID STAT  PPID  PGID TPGID COMMAND
   9200 T    97412  9200 11341 claude
  # Note: TPGID (terminal foreground pgrp) = shell pid, while claude's PGID = its own pid.
  # claude is therefore NOT the TTY's foreground process group when it runs tcsetattr.

  # sample(1) stack of the stopped process:
  1724 ThreadDispatchQueue_1: com.apple.main-thread
    + ... (claude internals) ...
    +   1724 tcsetattr  (in libsystem_c.dylib) + 172
    +     1724 ioctl  (in libsystem_kernel.dylib) + 36
    +       1724 __ioctl  (in libsystem_kernel.dylib) + 8
  # tcsetattr from a non-foreground process group → SIGTTOU → stopped.

---

1. macOS 26.3.1 / Apple Silicon / claude code 2.1.129
  2. Open a new terminal window (Ghostty or Terminal.app — both reproduce)
  3. In the resulting interactive shell (fish or zsh), run: claude
  4. Banner renders, then the shell reports the job suspended.

  Notes:
  - Reproduces under `fish --no-config` (rules out user fish config / hooks)
  - Reproduces under interactive zsh in a freshly launched Terminal.app window
  - Pre-existing shell sessions started >18h ago do NOT reproduce — `claude`
    launches fine in them.

---

$ ps -o pid,ppid,tty,stat,etime,%cpu,command -p 1207 1152 68247 68155
    PID  PPID TTY      STAT  ELAPSED  %CPU COMMAND
   1152  1151 ttys000  S    21:44:54   0.0 -/opt/homebrew/bin/fish --login
   1207  1152 ttys000  S+   21:44:45   0.1 claude
  68155 68154 ttys007  S    18:43:17   0.0 -/opt/homebrew/bin/fish --login
  68247 68155 ttys007  S+   18:43:15   0.0 claude
RAW_BUFFERClick to expand / collapse

Preflight Checklist

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

What's Wrong?

On macOS 26.3.1 (Darwin 25.3.0) Apple Silicon, launching claude from any newly opened terminal session causes the process to be immediately suspended by the kernel with SIGTTOU (or SIGTTIN) right after the banner renders. The shell reports e.g. fish: Job 1, 'claude' has stopped or zsh: suspended (tty input) claude.

The bug is reproducible across:

  • fish 4.5.0 (with and without --no-config)
  • interactive zsh in a freshly opened Terminal.app window
  • inside Ghostty as well as Terminal.app

Striking data point: two claude processes that I started ~21 hours and ~18 hours ago (before the bug appeared) are still running fine on long-lived shell sessions on my machine. Only newly-launched shell sessions are affected. This implies the responsible change happened in the last 24h on this machine.

What Should Happen?

  $ claude
   ▐▛███▜▌   Claude Code v2.1.129
  ▝▜█████▛▘  Opus 4.7 with high effort · API Usage Billing
    ▘▘ ▝▝    /Users/<me>

  ❯ fish: Job 1, 'claude' has stopped

  # In zsh:
  $ claude
  ... (banner) ...
  zsh: suspended (tty input)  claude
  zsh: error on TTY read: Input/output error
  [Process completed]

  # Diagnostic snapshot of the stopped process:
  $ ps -o pid,stat,ppid,pgid,tpgid,command -p <claude-pid>
    PID STAT  PPID  PGID TPGID COMMAND
   9200 T    97412  9200 11341 claude
  # Note: TPGID (terminal foreground pgrp) = shell pid, while claude's PGID = its own pid.
  # claude is therefore NOT the TTY's foreground process group when it runs tcsetattr.

  # sample(1) stack of the stopped process:
  1724 Thread … DispatchQueue_1: com.apple.main-thread
    + ... (claude internals) ...
    +   1724 tcsetattr  (in libsystem_c.dylib) + 172
    +     1724 ioctl  (in libsystem_kernel.dylib) + 36
    +       1724 __ioctl  (in libsystem_kernel.dylib) + 8
  # tcsetattr from a non-foreground process group → SIGTTOU → stopped.

Error Messages/Logs

1. macOS 26.3.1 / Apple Silicon / claude code 2.1.129
  2. Open a new terminal window (Ghostty or Terminal.app — both reproduce)
  3. In the resulting interactive shell (fish or zsh), run: claude
  4. Banner renders, then the shell reports the job suspended.

  Notes:
  - Reproduces under `fish --no-config` (rules out user fish config / hooks)
  - Reproduces under interactive zsh in a freshly launched Terminal.app window
  - Pre-existing shell sessions started >18h ago do NOT reproduce — `claude`
    launches fine in them.

Steps to Reproduce

  1. macOS 26.3.1 / Apple Silicon / claude code 2.1.129
  2. Open a new terminal window (Ghostty or Terminal.app — both reproduce)
  3. In the resulting interactive shell (fish or zsh), run: claude
  4. Banner renders, then the shell reports the job suspended.
<img width="2404" height="410" alt="Image" src="https://github.com/user-attachments/assets/24477fc3-7315-4155-80c2-c08940685f2e" />

Notes:

  • Reproduces under fish --no-config (rules out user fish config / hooks)
  • Reproduces under interactive zsh in a freshly launched Terminal.app window
  • Pre-existing shell sessions started >18h ago do NOT reproduce — claude launches fine in them.

Claude Model

Opus

Is this a regression?

I don't know — I don't have a confirmed last-working version, but two pre-existing claude processes started ~24h ago are still running, suggesting something on the system or in the binary changed within the last day.

Last Working Version

No response

Claude Code Version

2.1.129 (Claude Code)

Platform

Anthropic API

Operating System

macOS (specifically macOS 26.3.1 / Darwin 25.3.0, arm64)

Terminal/Shell

Other — Ghostty (also reproduces in Apple Terminal.app); shells tested: fish 4.5.0 and zsh.

Additional Information

Suspected root cause: The claude binary appears to call tcsetattr() during startup without first ensuring it is the controlling TTY's foreground process group, and without ignoring SIGTTOU. POSIX requires that tcsetattr() from a process not in the foreground process group raises SIGTTOU on the calling process (regardless of the orphaned-process-group exception). When the host shell does not call tcsetpgrp() to hand TTY ownership to claude before exec, claude is stopped immediately.

This explains the observed cross-shell reproducibility: neither fish 4.5.0 nor zsh appears to issue tcsetpgrp() for foreground external commands in the default interactive job-control mode (relying on the child to manage its own process group).

Suggested fixes (any one of these would prevent the suspension):

  1. At startup, before any tcsetattr(): signal(SIGTTOU, SIG_IGN);
  2. Or explicitly take TTY ownership: setpgid(0, 0); if (isatty(STDIN_FILENO)) tcsetpgrp(STDIN_FILENO, getpgrp());
  3. Or guard the tcsetattr() call: if (tcgetpgrp(STDIN_FILENO) != getpgrp()) { /* skip raw-mode setup */ }

Workarounds tried on user side (all unsatisfactory):

  • status job-control full in fish — does not consistently fix
  • script -q /dev/null claude (BSD script PTY wrapper) — claude starts but stdin forwarding is unreliable; claude exits soon after
  • python3 pty.spawn — works for input but PTY size defaults to 80×24 and SIGWINCH is not forwarded by stdlib pty.spawn

Diagnostic evidence in this report was collected with:

  • ps -o pid,stat,ppid,pgid,tpgid,command -p <pid>
  • sample <pid> 2 -mayDie (macOS native sampler)
  • lsof -p <pid>
  • comparison across fish/zsh and across pre-existing vs new shell sessions.

Two pre-existing claude processes that started ~21h and ~18h ago are still running fine on this machine. Only newly-launched shell sessions reproduce the bug. Snapshot:

  $ ps -o pid,ppid,tty,stat,etime,%cpu,command -p 1207 1152 68247 68155
    PID  PPID TTY      STAT  ELAPSED  %CPU COMMAND
   1152  1151 ttys000  S    21:44:54   0.0 -/opt/homebrew/bin/fish --login
   1207  1152 ttys000  S+   21:44:45   0.1 claude
  68155 68154 ttys007  S    18:43:17   0.0 -/opt/homebrew/bin/fish --login
  68247 68155 ttys007  S+   18:43:15   0.0 claude

Note STAT=S+ (foreground process group, sleeping) for the healthy claudes, versus STAT=T (stopped, SIGTTOU) for any newly-launched claude in this same machine right now. Whatever made the foreground-group handoff work for these two pre-existing sessions stopped working after they were started.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING