Statusline command has no documented way to determine terminal width (no JSON field, `/dev/tty` not readable, `$COLUMNS` not set)

Resolved 💬 3 comments Opened May 18, 2026 by Liquidmasl Closed May 18, 2026

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 ideally terminal.height) to the stdin JSON. This is the cleanest API and matches how other tools expose terminal info.
  • B: restore /dev/tty accessibility in the statusline subprocess, so stty size < /dev/tty works as it used to in 2.1.138.
  • C: export $COLUMNS to 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 .version in 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.

View original on GitHub ↗

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