claude-code - 💡(How to fix) Fix Statusline command has no documented way to determine terminal width (no JSON field, `/dev/tty` not readable, `$COLUMNS` not set)

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…

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:

SourceResult
terminal.width (or any path) in stdin JSONfield does not exist
stty size < /dev/ttyfails — /dev/tty not readable (regression from earlier versions)
tput colsreturns terminfo default (80), not actual terminal width
$COLUMNS env varnot 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.

Root Cause

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.

Fix Action

Fix / Workaround

This compounds with anthropics/claude-code#58028 — that issue's workaround was exactly stty size < /dev/tty, which now no longer works.

Code Example

#!/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)"

---

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

---

# 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)

---

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
RAW_BUFFERClick to expand / collapse

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:

SourceResult
terminal.width (or any path) in stdin JSONfield does not exist
stty size < /dev/ttyfails — /dev/tty not readable (regression from earlier versions)
tput colsreturns terminfo default (80), not actual terminal width
$COLUMNS env varnot 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.

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

claude-code - 💡(How to fix) Fix Statusline command has no documented way to determine terminal width (no JSON field, `/dev/tty` not readable, `$COLUMNS` not set)