You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
statusLine: pass terminal columns to command via stdin or env, and account for visual line wrapping #22115
Custom statusLine commands run as child processes with piped stdout. In this context, there is no way to detect the terminal width:
process.stdout.columns -> undefined (pipe, not TTY)
process.stderr.columns -> undefined
process.env.COLUMNS -> undefined
tput cols, mode con, stty size -> all fail in pipe context
This means the statusLine command cannot know how wide the terminal is.
Why it matters
Claude Code calculates the status line's row count by counting \n characters. However, if a line exceeds the actual terminal width, it visually wraps to the next row. Claude Code doesn't account for this visual wrapping, causing:
Input area height mismatch - user presses Shift+Enter to add a new line, but the input area doesn't grow (or grows to wrong height)
Excessive blank lines - vertical layout breaks with large empty spaces below the conversation
Since the statusLine command cannot detect terminal width, it cannot truncate its output to prevent wrapping.
Option B: Pass terminal width via environment variable (COLUMNS) when spawning the statusLine child process.
Option C (complementary): Claude Code should also account for visual line wrapping when calculating status line height internally, by dividing each line's visible width by the terminal column count.
Environment
Claude Code v2.1.27
Windows 11
Tested with Node.js statusLine command
Reacted by Vinicius Vinhas, Agilis, Jeff Squyres, Chris Leishman, Enzo Verbanaz, maxkuzn, joseph bartels, David James, mickey-mikey, Daniel Aquino and 13 more
This issue is specifically about the statusLine child process having no way to detect terminal width (process.stdout.columns, stderr.columns, env.COLUMNS are all undefined in the piped child process). This is a feature request to pass terminal dimensions via stdin JSON or environment variables.
The root cause is different: those issues are about Ink's viewport height calculation; this one is about the statusLine command having no access to terminal geometry, making it impossible to prevent visual line wrapping that breaks Claude Code's row count.
Additional findings from Windows 11 + ConPTY + claude-hud
Confirming and expanding on this issue with concrete data from debugging claude-hud (statusLine plugin).
Width detection: all methods fail in ConPTY piped subprocess
Method
Result
Tracks resize?
process.stdout.columns
undefined
N/A
process.stderr.columns
undefined
N/A
process.env.COLUMNS
undefined
N/A
mode con (cmd.exe)
120 (buffer width, not window)
No - always returns initial buffer size
tput cols (Git Bash)
80 (default fallback)
No - always returns 80
The wrap=truncate compounding issue
Even when the statusLine command wraps its output into multiple shorter lines (via separate console.log calls), Claude Code's Text wrap=truncate truncates each line with ... if it exceeds the Ink rendering width. Since the subprocess cannot detect the actual rendering width, it is impossible to produce correctly-sized output.
Tested behavior:
statusLine outputs 2 lines (via separate console.log calls) - Claude Code renders both lines OK
Any single line exceeding Ink rendering width - truncated with ...
Window resize - mode con still returns stale value - wrapping breaks
Strongest case for Option B (COLUMNS env var)
Setting COLUMNS in the child process environment would:
Be immediately available (process.env.COLUMNS) - no subprocess overhead
Track window resize (if Claude Code updates it on each invocation)
Allow statusLine commands to wrap/truncate correctly
Be a one-line fix on the Claude Code side
Option A (stdin JSON) would also work but requires statusLine commands to parse stdin, which not all implementations do.
mmaid — Mermaid diagram renderer, renders at 80 cols regardless of actual terminal width
chart-tool — ANSI bar charts, sparklines, histograms all assume 80 cols
embed-status / ways-stats / governance-report — CLI dashboards that could use the full width for tables
From inside a Bash tool call:
COLUMNS=0
tput cols → 80 (default, not real)
stty size → fails (no tty)
The fix is straightforward: Claude Code already knows the terminal dimensions — just pass COLUMNS and LINES as env vars to child processes (hooks, Bash tool, statusLine commands). This would immediately benefit any tool that renders structured terminal output.
To clarify the severity: this doesn't just produce suboptimal output — it can actively break rendering. Tools that detect width via tput cols get 80 (the hardcoded default), but if the real terminal is narrower (split panes, tiling WMs, mobile), the output wraps unpredictably and becomes unreadable. And if wider, you're wasting half the screen.
The COLUMNS=0 value is particularly misleading — tools that check for it get zero, which some interpret as "no terminal" rather than "unknown width."
Adding data from another corner this hits: JetBrains IDE terminals (JediTerm — PyCharm, IntelliJ, etc.) on Windows.
Every probe confirmed from a subprocess matching the statusline contract (stdin piped, MSYS2 Git Bash environment):
Method
Result
stty size </dev/tty
/dev/tty: No such device or address
[Console]::WindowWidth (PowerShell)
Invalid handle
mode.com con
stub 120
tput cols
stub 80
$env:COLUMNS / JediTerm env vars
unset (only TERMINAL_EMULATOR and TERM_SESSION_ID are exported — no width field)
The MSYS2 /dev/tty case is notable — the subprocess was spawned with pipes (no pty), so even the POSIX-style last-resort of reading the controlling terminal is unavailable. That's the same pattern @kiki830621 reported for macOS (ENXIO), just with a different error surface.
Interim workaround on my end: a PROMPT_COMMAND hook writes tput cols to a file from the user's interactive shell, and the statusline script reads it. Works but requires pressing Enter after each resize, and only covers bash (PowerShell users need a parallel prompt function hook). Solved for my setup, but a genuinely brittle ecosystem problem.
Strong +1 on Option B (env vars) — would also fix hooks and Bash tool calls as @aaronsb noted, not just statusline.
+1 — hitting this on Claude Code 2.1.136 with a multi-row bash statusline (~14 lines: machine/branch/PR header, per-flow Metaflow progress rows, per-file git churn rows). Symptom: only line 1 reaches the TUI; lines 2+ are silently absent (not wrapped, not overlapped — just gone).
Confirms #22115's diagnosis from a different angle: the statusline child genuinely has no way to detect terminal width:
process.stdout.columns → undefined (pipe)
$COLUMNS → unset
tput cols → falls back to 80
stty size < /dev/tty → /dev/tty: No such device or address (no controlling TTY for the child)
So even a well-behaved script that wants to self-truncate per line can't.
Extra wrinkle worth noting on the renderer side: our hidden lines carry OSC 8 hyperlinks (\033]8;;URL\a…\033]8;;\a) and ANSI SGR. Visible width per line is modest (~80–120 cols), but raw byte length is 300+ per line. If the row-height accounting measures bytes rather than visible width, that alone would explain why lines 2+ vanish on terminals that should comfortably fit them visually.
Sharing a downstream workaround for anyone hitting this in Python statuslines:
claude-status v0.5.7 (released today) walks a 7-step fallback chain to recover terminal width when the subprocess context hides it: stdin terminal.columns (forward-compat for whenever this issue lands) → COLUMNS env → shutil.get_terminal_size → os.get_terminal_size(fd) for stderr/stdout/stdin → stty size < /dev/tty → tput cols 2>/dev/tty → safe default. Each step is wrapped in try/except so missing tools / closed /dev/tty fall through silently, with a 1s timeout on the subprocess probes.
Real-world impact in our case: a 165-col terminal that previously rendered only ~83 chars on Line 2 now renders ~137 chars (5 sections → 11 sections). Pure stdlib, no daemon, no perf cost on the cheap paths.
Implementation: _detect_terminal_width() in cli.py. Happy to extract the helper into a standalone snippet if it'd be useful for other plugin authors hitting this.
Still very much in favor of the upstream fix — the cleanest signal will always be Anthropic putting terminal.columns in stdin JSON. Pinging in case it helps prioritize.
Sharing a downstream workaround for anyone hitting this in Python statuslines:
claude-status v0.5.7 (released today) walks a 7-step fallback chain to recover terminal width when the subprocess context hides it: stdin terminal.columns (forward-compat for whenever this issue lands) → COLUMNS env → shutil.get_terminal_size → os.get_terminal_size(fd) for stderr/stdout/stdin → stty size < /dev/tty → tput cols 2>/dev/tty → safe default. Each step is wrapped in try/except so missing tools / closed /dev/tty fall through silently, with a 1s timeout on the subprocess probes.
Real-world impact in our case: a 165-col terminal that previously rendered only ~83 chars on Line 2 now renders ~137 chars (5 sections → 11 sections). Pure stdlib, no daemon, no perf cost on the cheap paths.
Implementation: _detect_terminal_width() in cli.py. Happy to extract the helper into a standalone snippet if it'd be useful for other plugin authors hitting this.
Still very much in favor of the upstream fix — the cleanest signal will always be Anthropic putting terminal.columns in stdin JSON. Pinging in case it helps prioritize.
wouldn't mind having a snippet or gist to play around with. thanks. but this does feel like a gap in cc that could be easily implemented especially as they develop the interface and status line with multi agent support. so, continuing the comments chain here for now seems like the best path to raise awareness and hopefully get future support? 🤞
+1 — confirming this regressed further in Claude Code 2.1.139. The release note:
Fixed a bug where a hook writing to the terminal could corrupt an on-screen interactive prompt; hooks now run without terminal access
"Hooks now run without terminal access" closes the last TTY-based escape hatch that the workaround chains in this thread rely on. Observed on macOS, 2.1.139, actual terminal 182 cols:
process.stdout/stderr.columns → undefined (piped, was already true)
COLUMNS env → unset (Claude Code does not forward it)
stty size < /dev/tty → device not configured (no controlling TTY, was sometimes still reachable before 2.1.139 depending on invocation path)
tput cols → 80 (its no-TTY stub fallback)
All four signals now either fail or return a misleading stub. Any "> N" sanity threshold under 80 will silently accept the tput stub and pin statusline rendering at an 80-col layout regardless of the user's real terminal width. I tripped this directly: a conservative < 70 threshold that worked fine through 2.1.138 stopped working in 2.1.139 because tput's 80 passes the check.
The practical effect is that after 2.1.139, COLUMNS passed via env or a terminal.columns field in stdin is the only path that can work. The fallback chains this thread has been building up (tput, stty, /dev/tty, etc.) are now entirely dead code on macOS and, I suspect, on Linux too — just more obviously than before.
Strongly preferring Option A (terminal: { columns, rows } in stdin JSON) from the original issue. Option B (COLUMNS env) would also solve it and is arguably cheaper to ship — with the caveat that it won't adapt to mid-session terminal resizes unless the hook is re-invoked with an updated env on each refresh tick.
+1 from a Go statusline implementation (ccstatus, I'm the author).
Option A (terminal: { columns, rows } in stdin JSON) would be great.
Most of the thread is Node/PowerShell/Python, so just adding a Go data point: we hit the exact same wall — the statusline child has no TTY, so COLUMNS, tput cols, /dev/tty etc. all fail or lie.
What we do today is a workaround: walk up the process tree until we find an ancestor that still owns the terminal, and read the size from there. When it works, it works well — the screenshots below are from Ghostty, where the layout adapts cleanly as I resize the terminal:
But "works in Ghostty on macOS" is the trap. It's a compromise, not a fix:
it does nothing on Windows / WSL / IDE terminals — exactly where this thread reports the worst breakage
when it can't find the width, the status line falls back to 80 columns and renders wrong, with no signal that anything went off
we still ship a manual terminalWidth config option to paper over the cases it misses — same hand-maintained-width situation everyone else here describes
A single terminal.columns field in the stdin JSON (which subagentStatusLine already gets as columns) would let us drop all of that. Happy to test a prerelease.
+1 — independent confirmation of the 2.1.139 regression from a different stack: Linux (NixOS), kitty terminal (TERM=xterm-kitty), real terminal width ~220 cols, custom Go statusline (cc-tools).
Probed from inside the Claude subprocess environment on 2.1.139:
COLUMNS → "0" (set, but zero)
TMUX, SSH_TTY → empty
open("/dev/tty") → ENXIO ("no such device or address")
ioctl(TIOCGWINSZ) on stderr/stdout/stdin → ENOTTY
tput cols → 80 (terminfo default for xterm-kitty)
stty size → fails (no controlling tty)
The render pins to ~80 cols against the left edge regardless of the actual terminal — exactly what @jeremy-newhouse described last week. Reproducible deterministically with echo '{...}' | cc-tools-statusline invoked from any Bash-tool-like context.
One thing worth highlighting from the table above: tput cols confidently returns 80, which silently passes any "> N" sanity threshold under 80. Statuslines that previously fell back to a sensible default (e.g., 200) when they detected "no real terminal" now happily render an 80-col layout into a 220-col window, because nothing in the chain reports failure — tput lies with a straight face.
Strong +1 on Option A (terminal.columns in stdin JSON), with the parity argument: subagentStatusLine already gets a columns field; the parent statusLine payload just doesn't.
Implementing @moonD4rk's parent-process-tree walk in cc-tools locally as a workaround until then — with the documented caveat that it only helps on real-pty hosts (Linux, macOS sans IDE).
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.
Activity
github-actions commentedon Jan 31, 2026
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
MeroZemory commentedon Jan 31, 2026
This is not a duplicate of the suggested issues.
process.stdout.columns,stderr.columns,env.COLUMNSare allundefinedin the piped child process). This is a feature request to pass terminal dimensions via stdin JSON or environment variables.The root cause is different: those issues are about Ink's viewport height calculation; this one is about the statusLine command having no access to terminal geometry, making it impossible to prevent visual line wrapping that breaks Claude Code's row count.
jameswasher commentedon Feb 16, 2026
This also causes vertical rendering on startup until the TUI initializes.
jsquyres commentedon Mar 3, 2026
General +1 on the idea: would love it if Claude passed the column width down to the status line somehow.
JYHTHEGREAT commentedon Mar 9, 2026
Additional findings from Windows 11 + ConPTY + claude-hud
Confirming and expanding on this issue with concrete data from debugging claude-hud (statusLine plugin).
Width detection: all methods fail in ConPTY piped subprocess
The wrap=truncate compounding issue
Even when the statusLine command wraps its output into multiple shorter lines (via separate console.log calls), Claude Code's Text wrap=truncate truncates each line with ... if it exceeds the Ink rendering width. Since the subprocess cannot detect the actual rendering width, it is impossible to produce correctly-sized output.
Tested behavior:
Strongest case for Option B (COLUMNS env var)
Setting COLUMNS in the child process environment would:
Option A (stdin JSON) would also work but requires statusLine commands to parse stdin, which not all implementations do.
Environment
aaronsb commentedon Mar 24, 2026
This affects more than just statusLine — any tool invoked via the Bash tool or hooks that renders terminal UI is blind to the actual width.
Concrete examples from claude-code-config:
From inside a Bash tool call:
The fix is straightforward: Claude Code already knows the terminal dimensions — just pass
COLUMNSandLINESas env vars to child processes (hooks, Bash tool, statusLine commands). This would immediately benefit any tool that renders structured terminal output.aaronsb commentedon Mar 24, 2026
To clarify the severity: this doesn't just produce suboptimal output — it can actively break rendering. Tools that detect width via
tput colsget 80 (the hardcoded default), but if the real terminal is narrower (split panes, tiling WMs, mobile), the output wraps unpredictably and becomes unreadable. And if wider, you're wasting half the screen.The
COLUMNS=0value is particularly misleading — tools that check for it get zero, which some interpret as "no terminal" rather than "unknown width."5 remaining items
carlospinto4p commentedon Apr 22, 2026
Adding data from another corner this hits: JetBrains IDE terminals (JediTerm — PyCharm, IntelliJ, etc.) on Windows.
Every probe confirmed from a subprocess matching the statusline contract (stdin piped, MSYS2 Git Bash environment):
stty size </dev/tty/dev/tty: No such device or address[Console]::WindowWidth(PowerShell)Invalid handlemode.com con120tput cols80$env:COLUMNS/ JediTerm env varsTERMINAL_EMULATORandTERM_SESSION_IDare exported — no width field)The MSYS2
/dev/ttycase is notable — the subprocess was spawned with pipes (no pty), so even the POSIX-style last-resort of reading the controlling terminal is unavailable. That's the same pattern @kiki830621 reported for macOS (ENXIO), just with a different error surface.Interim workaround on my end: a
PROMPT_COMMANDhook writestput colsto a file from the user's interactive shell, and the statusline script reads it. Works but requires pressing Enter after each resize, and only covers bash (PowerShell users need a parallelpromptfunction hook). Solved for my setup, but a genuinely brittle ecosystem problem.Strong +1 on Option B (env vars) — would also fix hooks and Bash tool calls as @aaronsb noted, not just statusline.
alchemymakemoney commentedon May 8, 2026
+1 — hitting this on Claude Code 2.1.136 with a multi-row bash statusline (~14 lines: machine/branch/PR header, per-flow Metaflow progress rows, per-file git churn rows). Symptom: only line 1 reaches the TUI; lines 2+ are silently absent (not wrapped, not overlapped — just gone).
Confirms #22115's diagnosis from a different angle: the statusline child genuinely has no way to detect terminal width:
process.stdout.columns→ undefined (pipe)$COLUMNS→ unsettput cols→ falls back to 80stty size < /dev/tty→/dev/tty: No such device or address(no controlling TTY for the child)So even a well-behaved script that wants to self-truncate per line can't.
Extra wrinkle worth noting on the renderer side: our hidden lines carry OSC 8 hyperlinks (
\033]8;;URL\a…\033]8;;\a) and ANSI SGR. Visible width per line is modest (~80–120 cols), but raw byte length is 300+ per line. If the row-height accounting measures bytes rather than visible width, that alone would explain why lines 2+ vanish on terminals that should comfortably fit them visually.Asks (in order of usefulness):
terminal.columnsin the stdin JSON (Option A in statusLine: pass terminal columns to command via stdin or env, and account for visual line wrapping #22115). Minimum viable; lets every script self-truncate.statusLine.maxLinescap so scripts know the budget.Happy to test a prerelease.
feat: v0.5.7 — recover terminal width hidden from subprocess + guard …
mkalkere commentedon May 10, 2026
Sharing a downstream workaround for anyone hitting this in Python statuslines:
claude-status v0.5.7 (released today) walks a 7-step fallback chain to recover terminal width when the subprocess context hides it: stdin
terminal.columns(forward-compat for whenever this issue lands) →COLUMNSenv →shutil.get_terminal_size→os.get_terminal_size(fd)for stderr/stdout/stdin →stty size < /dev/tty→tput cols 2>/dev/tty→ safe default. Each step is wrapped in try/except so missing tools / closed/dev/ttyfall through silently, with a 1s timeout on the subprocess probes.Real-world impact in our case: a 165-col terminal that previously rendered only ~83 chars on Line 2 now renders ~137 chars (5 sections → 11 sections). Pure stdlib, no daemon, no perf cost on the cheap paths.
Implementation:
_detect_terminal_width()in cli.py. Happy to extract the helper into a standalone snippet if it'd be useful for other plugin authors hitting this.Still very much in favor of the upstream fix — the cleanest signal will always be Anthropic putting
terminal.columnsin stdin JSON. Pinging in case it helps prioritize.seanmartinsmith commentedon May 10, 2026
wouldn't mind having a snippet or gist to play around with. thanks. but this does feel like a gap in cc that could be easily implemented especially as they develop the interface and status line with multi agent support. so, continuing the comments chain here for now seems like the best path to raise awareness and hopefully get future support? 🤞
jeremy-newhouse commentedon May 12, 2026
+1 — confirming this regressed further in Claude Code 2.1.139. The release note:
"Hooks now run without terminal access" closes the last TTY-based escape hatch that the workaround chains in this thread rely on. Observed on macOS, 2.1.139, actual terminal 182 cols:
process.stdout/stderr.columns→undefined(piped, was already true)COLUMNSenv → unset (Claude Code does not forward it)stty size < /dev/tty→device not configured(no controlling TTY, was sometimes still reachable before 2.1.139 depending on invocation path)tput cols→80(its no-TTY stub fallback)All four signals now either fail or return a misleading stub. Any "> N" sanity threshold under 80 will silently accept the
tputstub and pin statusline rendering at an 80-col layout regardless of the user's real terminal width. I tripped this directly: a conservative< 70threshold that worked fine through 2.1.138 stopped working in 2.1.139 becausetput's 80 passes the check.The practical effect is that after 2.1.139,
COLUMNSpassed via env or aterminal.columnsfield in stdin is the only path that can work. The fallback chains this thread has been building up (tput, stty, /dev/tty, etc.) are now entirely dead code on macOS and, I suspect, on Linux too — just more obviously than before.Strongly preferring Option A (
terminal: { columns, rows }in stdin JSON) from the original issue. Option B (COLUMNSenv) would also solve it and is arguably cheaper to ship — with the caveat that it won't adapt to mid-session terminal resizes unless the hook is re-invoked with an updated env on each refresh tick.Happy to test a prerelease.
moonD4rk commentedon May 12, 2026
+1 from a Go statusline implementation (ccstatus, I'm the author).
Option A (
terminal: { columns, rows }in stdin JSON) would be great.Most of the thread is Node/PowerShell/Python, so just adding a Go data point: we hit the exact same wall — the statusline child has no TTY, so
COLUMNS,tput cols,/dev/ttyetc. all fail or lie.What we do today is a workaround: walk up the process tree until we find an ancestor that still owns the terminal, and read the size from there. When it works, it works well — the screenshots below are from Ghostty, where the layout adapts cleanly as I resize the terminal:
But "works in Ghostty on macOS" is the trap. It's a compromise, not a fix:
terminalWidthconfig option to paper over the cases it misses — same hand-maintained-width situation everyone else here describesA single
terminal.columnsfield in the stdin JSON (whichsubagentStatusLinealready gets ascolumns) would let us drop all of that. Happy to test a prerelease.joshsymonds commentedon May 12, 2026
+1 — independent confirmation of the 2.1.139 regression from a different stack: Linux (NixOS), kitty terminal (
TERM=xterm-kitty), real terminal width ~220 cols, custom Go statusline (cc-tools).Probed from inside the Claude subprocess environment on 2.1.139:
The render pins to ~80 cols against the left edge regardless of the actual terminal — exactly what @jeremy-newhouse described last week. Reproducible deterministically with
echo '{...}' | cc-tools-statuslineinvoked from any Bash-tool-like context.One thing worth highlighting from the table above:
tput colsconfidently returns80, which silently passes any "> N" sanity threshold under 80. Statuslines that previously fell back to a sensible default (e.g., 200) when they detected "no real terminal" now happily render an 80-col layout into a 220-col window, because nothing in the chain reports failure —tputlies with a straight face.Strong +1 on Option A (
terminal.columnsin stdin JSON), with the parity argument:subagentStatusLinealready gets acolumnsfield; the parentstatusLinepayload just doesn't.Implementing @moonD4rk's parent-process-tree walk in cc-tools locally as a workaround until then — with the documented caveat that it only helps on real-pty hosts (Linux, macOS sans IDE).
fix(statusline): detect width via ancestor TTY when subprocess lacks one
claude commentedon May 28, 2026
This issue was fixed as of version 2.1.153.
feat(fill): add $fill layout token for right-alignment
feat: add $fill layout token (Starship-style right-alignment) (#189)
github-actions commentedon Jul 26, 2026
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.