Skip to content

fix(terminal): use stty -F/-f to read tty size in detached spawn#377

Merged
sirmalloc merged 2 commits into
sirmalloc:mainfrom
AgarwalPragy:fix/stty-portable-probe
May 13, 2026
Merged

fix(terminal): use stty -F/-f to read tty size in detached spawn#377
sirmalloc merged 2 commits into
sirmalloc:mainfrom
AgarwalPragy:fix/stty-portable-probe

Conversation

@AgarwalPragy
Copy link
Copy Markdown
Contributor

Summary

Fixes the TTY-width probe under Claude Code ≥ 2.1.139, which spawns the statusline without a controlling terminal:

Fixed a bug where a hook writing to the terminal could corrupt an on-screen interactive prompt; hooks now run without terminal access.

The existing stty size < /dev/${tty} form fails with ENOTTY in that context.

Closes #376.

Change

src/utils/terminal.tsgetWidthForTTY now tries three variants in order; first positive integer wins:

  1. stty -F <path> size (GNU coreutils, Linux)
  2. stty -f <path> size (BSD, macOS)
  3. stty size < <path> (legacy fallback)

Test plan

  • New unit test for the BSD fallback case (-F errors, -f succeeds)
  • Existing probe tests updated for the new command shape
  • All 1303 tests pass; lint clean
  • Live-verified on Linux + Claude Code 2.1.140: detection goes from null → real terminal width

AgarwalPragy and others added 2 commits May 13, 2026 21:31
The TTY-width probe walked ancestor processes to find a controlling
PTY and ran `stty size < /dev/${tty}` to read its dimensions. That
form fails with ENOTTY on Linux when the calling process has no
controlling terminal — which is now the case under Claude Code
>= 2.1.139, whose changelog reads "hooks now run without terminal
access". The statusLine spawn is hardened the same way. Probe falls
back to `tput cols` (= 80), flexMode "full-minus-40" collapses to
40 columns, and the statusline truncates regardless of the real
terminal width.

GNU coreutils `stty -F <path>` and BSD `stty -f <path>` open the
device themselves (with O_NOCTTY semantics) and succeed regardless
of controlling-tty status. Try `-F` then `-f` then the historical
redirect form so we keep working on every stty variant.

Verified: on a Linux+ptyxis spawn under Claude Code 2.1.140 the probe
goes from returning null to returning the real width (159 cols here)
without restarting the session.
@sirmalloc sirmalloc merged commit 9eccca2 into sirmalloc:main May 13, 2026
@sirmalloc
Copy link
Copy Markdown
Owner

Thanks, I'll publish this in the next release

sirmalloc added a commit that referenced this pull request May 17, 2026
…380)

* feat(terminal): honor CCSTATUSLINE_WIDTH env var to override probe

Provide an explicit width override so users can bypass the TTY probe
entirely when both ancestor-walk and `tput cols` fall through.

This is the fallback case the existing probe cannot solve on its own:
Claude Code >= 2.1.139 spawns statusline/hooks without terminal access,
and in some configurations (IDE integrations, nested shells, certain
agent-mode spawn paths) no ancestor process owns a TTY either. The
ancestor walk fails, `tput cols` returns 80, and the multi-line layout
truncates regardless of the actual iTerm2/terminal width.

PR #377 (`stty -F`/`stty -f`) covers the case where an ancestor does
hold a TTY but the legacy `< /dev/tty` form errors with ENOTTY. This
patch is complementary -- it handles the case where the ancestor walk
finds no TTY at all -- and gives users a knob today while upstream
work on passing `terminalWidth` via stdin JSON (#308) lands.

Change
------

`src/utils/terminal.ts` -- `probeTerminalWidth` now reads
`CCSTATUSLINE_WIDTH` before any platform check or probe. A valid
positive integer short-circuits with that value; anything else
(missing, empty, non-numeric, zero, negative) falls through to the
existing probe logic.

Usage
-----

Set the env var on the statusLine command in `~/.claude/settings.json`:

```json
"statusLine": {
  "type": "command",
  "command": "CCSTATUSLINE_WIDTH=200 ccstatusline"
}
```

Tests
-----

Four new cases in `src/utils/__tests__/terminal.test.ts`:

- override short-circuits probing entirely
- non-positive override (`0`) falls back to probing
- non-numeric override falls back to probing
- override applies on Windows where probing is otherwise disabled

`bunx vitest run src/utils/__tests__/terminal.test.ts` -> 12/12 pass.
Full-suite delta vs `main`: 4 new passing tests, zero new failures
(the pre-existing 105 env-specific failures in other suites are
unchanged on both branches).

`bun run lint` -> clean.

* docs: document terminal width override

---------

Co-authored-by: Matthew Breedlove <sirmalloc@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terminal width probe fails under Claude Code ≥ 2.1.139 (no controlling TTY for spawned statusline)

2 participants