Skip to content

statusLine: pass terminal columns to command via stdin or env, and account for visual line wrapping #22115

Closed

Description

@MeroZemory

Problem

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:

  1. 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)
  2. 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.

Reproduction

  1. Add to ~/.claude/settings.json:
{
  "statusLine": {
    "type": "command",
    "command": "node -e \"console.log('A'.repeat(200))\""
  }
}
  1. Start a Claude Code session
  2. Press Shift+Enter in the input area
  3. Observe: input area height is wrong, blank lines appear below

Proposed solution

Option A: Include terminal dimensions in the stdin JSON:

{
  "terminal": { "columns": 120, "rows": 40 },
  ...existing fields...
}

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

Activity

  1. github-actions commented on Jan 31, 2026

    @github-actions

    Found 3 possible duplicate issues:

    1. Blank lines render above input when typing after Ctrl+Enter newline #18785
    2. [Bug] Excessive blank lines causing continuous terminal scrolling #10472
    3. [BUG] Too much blank space at bottom of terminal #4136

    This issue will be automatically closed as a duplicate in 3 days.

    • If your issue is a duplicate, please close it and 👍 the existing issue instead
    • To prevent auto-closure, add a comment or 👎 this comment

    🤖 Generated with Claude Code

  2. MeroZemory commented on Jan 31, 2026

    @MeroZemory
    Author

    This is not a duplicate of the suggested issues.

    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.

  3. jameswasher commented on Feb 16, 2026

    @jameswasher

    This also causes vertical rendering on startup until the TUI initializes.

    Image
  4. jsquyres commented on Mar 3, 2026

    @jsquyres

    General +1 on the idea: would love it if Claude passed the column width down to the status line somehow.

  5. JYHTHEGREAT commented on Mar 9, 2026

    @JYHTHEGREAT

    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:

    1. Be immediately available (process.env.COLUMNS) - no subprocess overhead
    2. Track window resize (if Claude Code updates it on each invocation)
    3. Allow statusLine commands to wrap/truncate correctly
    4. 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.

    Environment

    • Claude Code: latest (PE binary, Windows)
    • Windows 11 Pro 10.0.26200
    • Windows Terminal with ConPTY
    • claude-hud v0.0.9
  6. aaronsb commented on Mar 24, 2026

    @aaronsb

    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:

    • 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.

  7. aaronsb commented on Mar 24, 2026

    @aaronsb

    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."

  8. 5 remaining items

  9. carlospinto4p commented on Apr 22, 2026

    @carlospinto4p

    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.

  10. alchemymakemoney commented on May 8, 2026

    @alchemymakemoney

    +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.

    Asks (in order of usefulness):

    1. Pass terminal.columns in 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.
    2. Strip ANSI/OSC escapes before measuring visible width when computing row count and wrap, if not already done.
    3. Optional: a documented statusLine.maxLines cap so scripts know the budget.

    Happy to test a prerelease.

  11. mkalkere commented on May 10, 2026

    @mkalkere

    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.

  12. seanmartinsmith commented on May 10, 2026

    @seanmartinsmith

    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? 🤞

  13. jeremy-newhouse commented on May 12, 2026

    @jeremy-newhouse

    +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.

    Happy to test a prerelease.

  14. moonD4rk commented on May 12, 2026

    @moonD4rk

    +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:

    Image Image

    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.

  15. joshsymonds commented on May 12, 2026

    @joshsymonds

    +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).

  16. claude commented on May 28, 2026

    @claude
    Contributor

    This issue was fixed as of version 2.1.153.

  17. github-actions commented on Jul 26, 2026

    @github-actions

    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.

  18. locked as resolved and limited conversation to collaborators on Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:tuibugSomething isn't workinghas reproHas detailed reproduction stepsplatform:windowsIssue specifically occurs on Windows

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions