Statusline command has no documented way to determine terminal width (no JSON field, `/dev/tty` not readable, `$COLUMNS` not set)
Summary
A custom statusline command needs the terminal width to render multi-line output correctly (wrap long content, decide whether to abbreviate paths, etc.). In Claude Code 2.1.143, none of the obvious width sources work inside the statusline subprocess:
| Source | Result |
|---|---|
| terminal.width (or any path) in stdin JSON | field does not exist |
| stty size < /dev/tty | fails — /dev/tty not readable (regression from earlier versions) |
| tput cols | returns terminfo default (80), not actual terminal width |
| $COLUMNS env var | not set in the subprocess |
That leaves statusline authors with no way to detect terminal width. Lines that should wrap or shorten render at full width and get clipped by Claude's own truncation, breaking multi-line statusline layouts.
Evidence
In a probe statusline script running under Claude Code 2.1.143 / macOS 25.4:
#!/bin/bash
input=$(cat)
echo "stty:$({ stty size < /dev/tty | awk '{print $2}'; } 2>/dev/null)"
echo "tput:$(tput cols 2>/dev/null)"
echo "COLUMNS:${COLUMNS:-unset}"
echo "json keys:$(echo "$input" | jq -r 'keys | join(",")')"
echo "json width-like fields:$(echo "$input" | jq -r 'paths | join(".")' | grep -iE 'term|cols|width|size' || echo none)"
Output observed in a real Claude Code session:
stty:
tput:80
COLUMNS:unset
json keys:context_window,cost,cwd,effort,exceeds_200k_tokens,fast_mode,model,output_style,rate_limits,session_id,session_name,thinking,transcript_path,version,workspace
json width-like fields:context_window.context_window_size
The single "width-like" field that showed up (context_window.context_window_size) is the LLM token context size, not terminal columns.
Regression note
In an earlier version (around 2.1.138), stty size < /dev/tty did return the real terminal width — we saw it tracking live resizes (71 → 80 → 131 cols). In 2.1.143 the same code returns nothing.
Confirmed root cause: the statusline subprocess in 2.1.143 has no controlling terminal. The tty command (the canonical test) inside the subprocess returns not a tty and exits 1:
# In the statusline command, on 2.1.143:
tty # → "not a tty", exit 1
[ -r /dev/tty ] # → true (filesystem permission check passes)
stty size < /dev/tty 2>&1 # → empty (open() fails because no controlling terminal)
/dev/tty exists as a file (so naive [ -r /dev/tty ] checks pass), but open("/dev/tty") fails because there's no controlling terminal behind it. That can only happen if the parent process (Claude Code) explicitly detached the subprocess from its controlling terminal at fork time — either via setsid(), opening in a new session, or similar.
Process tree from inside the statusline subprocess:
PID PPID COMMAND
31040 1 bash /Users/marcel/.claude/statusline.sh ← subprocess, PPID is init
31041 31040 bash /Users/marcel/.claude/statusline-modules/user/199-debug.sh
The PPID of 1 (init) is the signature of an intentional detachment — Claude is launching the statusline via setsid() or a double-fork, fully orphaning it from its own process group. That's why there's no controlling terminal: detached processes start a new session with no associated TTY.
This is an explicit child-process invocation choice on Claude Code's side, not something the statusline command can recover from inside the subprocess.
Why this matters
Statusline commands aren't ornamental — they're how users surface project state (current branch, model, context usage, rate limit, PR queue). Multi-line statuslines that flow modules across lines need to know terminal width to decide where to wrap. Without it, every overlong row gets tail-truncated with … by Claude itself, hiding information the user wanted to see.
A working stty size was effectively the de-facto API for terminal width. Closing that off without providing a replacement leaves no path forward for statusline authors.
Suggested fix
Pick whichever is easiest:
- A (best, also forward-compatible): add
terminal.width(and ideallyterminal.height) to the stdin JSON. This is the cleanest API and matches how other tools expose terminal info. - B: restore
/dev/ttyaccessibility in the statusline subprocess, sostty size < /dev/ttyworks as it used to in 2.1.138. - C: export
$COLUMNSto the statusline subprocess. Cheap to implement.
Any one of these unblocks statusline authors. (A) is the most discoverable and least platform-dependent.
Environment
- Claude Code: 2.1.143 (from
.versionin stdin JSON) - Terminals tested: iTerm2 Build 3.6.10, wezterm 20240203-110809-5046fc22
- OS: macOS 25.4 (Darwin)
- Shell: zsh
Related
This compounds with anthropics/claude-code#58028 — that issue's workaround was exactly stty size < /dev/tty, which now no longer works.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗