Conversation
… TTY On Linux, when the native /proc probe finds no ancestor with a tty on its stdio, probeTerminalWidth fell through to the portable walk: two `ps` spawns per ancestor (up to 8) and then `tput cols`. In a no-TTY spawn (detached Claude Code, IDE hosts, headless runs) `tput` answers 80 or $COLUMNS, so the result is numeric, the session "no TTY" cache (sirmalloc#501) never engages, and every render pays the whole walk again. The native walk already reads /proc/<pid>/stat for each ancestor. It now also reads tty_nr, the field `ps -o tty=` renders ("?" for 0). When every ancestor the ps walk would visit (including pid 1 and the one at the depth limit) has tty_nr == 0, that walk provably finds nothing and no stty ever runs, so it is skipped. `tput cols` is kept, so the returned width is unchanged. Any unreadable stat, a non-zero tty_nr (controlling terminal with redirected stdio, foreign-uid ancestor) or a non-Linux platform keeps the full fallback. Detached no-TTY run (setsid -f, strace): 8 spawns + ~80 PATH probes per render -> 1 spawn (tput) + ~10 probes; stdout byte-identical on the default/git0/heavy/livelike configs, with and without COLUMNS. 25-pass interleaved bench, load ~61 on 6 cores, CPU median: default 1989 -> 1471 ms (-26%), heavy 2318 -> 1880 ms (-19%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
psspawns per render on Linux when no ancestor has a terminal. The result is unchanged./procprobe (perf: stop re-probing the terminal width on every line of every render #501) finds no tty on any ancestor's stdio, we fall through to the portable walk: 2psper ancestor (up to 8 ancestors), thentput cols. With no TTY,tputreturns 80 or$COLUMNS. That result is a number, so the session "no TTY" cache never kicks in and every render pays for the whole walk again./proc/<pid>/stat. It now also readstty_nr, the fieldps -o tty=prints. If every ancestor thepswalk would visit hastty_nr == 0, that walk can't find anything, so we skip it.tput colsstill runs, so the width is the same.Details
terminal-native.tsaddsprobeTerminalNative(), which returns{ width, noControllingTTY }, andparseTtyNrFromStat().probeWidthNative()stays as a width-only wrapper.noControllingTTYis set only when all of these hold:tty_nr == 0;tty_nr == 0(thepswalk checks pid 1 too, e.g. a shell running as a container's init);tty_nr == 0.fdlinks can't be read);getTerminalWidthprecedence is unchanged:CCSTATUSLINE_WIDTH(feat(terminal): honorCCSTATUSLINE_WIDTHenv var to override probe #380), then the session "no TTY" cache (perf: stop re-probing the terminal width on every line of every render #501), then the probe. The cache is still written only for anullresult.psloop moved intoprobeAncestorWidth()without changes.src/utils/terminal.ts, so expect a small merge conflict): the two complement each other.COLUMNSbefore probing. On Claude Code 2.1.153+ that means zero spawns.COLUMNSisn't set: older Claude Code, other hosts, and manual runs. Today that path costs 7 or morepsspawns plustput. With this PR it costs onetput.tput colswithout a TTY only reports$COLUMNSor terminfo's default (80), so it could be replaced by readingCOLUMNSdirectly. That changes the no-COLUMNSresult from 80 tonull, so it belongs in its own PR.ps/sttywalk on every render, and a numeric width is never persisted. feat(terminal): read COLUMNS from Claude Code before probing #612 fixes that for current Claude Code. Asysctl/libproc-based walk could fix it otherwise, but I can't test macOS here, so I haven't implemented it.Measurements
Setup:
setsid -freparents the run to init, so no ancestor holds a pty. That matches Claude Code spawning the statusline with no terminal in its ancestry.COLUMNSunset, p1 payload, git cache warm.Spawn counts (
strace -f -e execve), per render:ps, 1 ×tput)tput)Stdout from main and patched is byte-identical on the default, git0, heavy and livelike configs, and also with
COLUMNS=137. Noterminal-width.jsoncache is written in either arm, which confirms the result is numeric (80).Interleaved bench: 25 passes, round-robin, CPU is user+sys including reaped children. Host load1 was min 53.9, median 61.3, max 66.8 on 6 cores, so compare ratios rather than absolute numbers.
node -e 0Checks
bun run lint(tsc + eslint): clean.bun run build: OK.bun test src/utils/__tests__/terminal*: 57 pass. New tests:parseTtyNrFromStat;probeTerminalNative:tty_nrset on an ancestor, on pid 1, and at the depth limit; unreadable stat; our owntty_nrignored; non-Linux;terminal.test.ts, a conclusive result gives exactly['tput cols'], an inconclusive one still runs theps/sttywalk, and a native width spawns nothing.bun teston this loaded host: 2297 pass, 74 fail. All failures are timing-related (fetchUsageData error handling, custom-command capture, Ink TUI menus). Re-running only the affected files on unmodifiedmaingave the same result there (174 pass, 69 fail on both). None of the failures are in terminal code.🤖 Generated with Claude Code