Repository navigation
Conversation
Read ancestry from /proc/<pid>/stat, locate the pty by readlinking
/proc/<pid>/fd/{0,1,2}, and get columns via TIOCGWINSZ (tty.WriteStream)
instead of spawning sh+ps / sh+stty+awk / sh+tput. Returns null off Linux
so the portable walk remains the fallback for macOS/BSD.
Verified against a real pty: probeWidthNative() returns 209, matching
'stty -F /dev/pts/7 size' (88 209), with zero spawns.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Persists probed widths to ~/.cache/ccstatusline/terminal-width.json, keyed
by session_id (width is per-terminal, so a global value would be wrong).
Atomic tmp+rename writes, entries pruned after an hour, corrupt/unwritable
cache treated as a miss and never fatal.
readCachedWidth returns a wrapper ({width} | null) so a cached null -- 'we
probed, there is no TTY' -- is a HIT, not a miss. Collapsing that would make
the no-TTY case re-probe forever, which is the bug this exists to fix.
ttlSeconds 0 disables caching (always probe). Note this deliberately differs
from gitCacheTtlSeconds, where 0 means 'never expire'.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude Code spawns the statusline without a TTY, so probeTerminalWidth() returns null. Callers read it as 'context.terminalWidth ?? getTerminalWidth()', so the null re-triggered the full ps/stty ancestor walk once per configured line. Cache the probe behind an explicit hasProbed flag so the negative result is memoized too. Co-Authored-By: Claude Opus 4.8 <[email protected]>
execSync with shell:'/bin/sh' spawned a shell in addition to the command, doubling every probe; the stty|awk pipe cost three processes. Call the binaries directly and parse 'rows cols' in JS. Drops the legacy 'stty size < /dev/tty' redirect form, which required a shell and is redundant with the -F/-f forms. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Chain is now: CCSTATUSLINE_WIDTH override -> in-process memo -> shared L2 disk cache -> probe (native /proc+TIOCGWINSZ on Linux, ps/stty/tput elsewhere). Only the entry point supplies a session_id, so only it reads or writes the shared cache; renderer.ts and widgets/TerminalWidth.ts keep their zero-arg calls and hit the memo. Adds terminalWidthCacheTtlSeconds (default 5, 0 disables) to Settings and RenderContext, and backfills it in two widget test fixtures that build a full Settings literal (zod .default() makes the key required on the output type). Reads process.platform at call time in terminal-native's default deps: a module-load snapshot ignored the per-test platform pin and ran the real Linux probe under darwin-pinned tests. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
…flicts Conflicts were additive on both sides (new RenderContext fields for terminal-width caching vs. git-CI-status review checks, and independent README changelog sections) — kept both.
- Serialize writes to the persistent width cache with a best-effort file lock (O_CREAT|O_EXCL, stale-lock recovery), closing a read-modify-write race where two concurrent ccstatusline processes (e.g. two Claude Code sessions) could silently drop each other's cache entry. - Only ever persist a "no TTY" probe result across process boundaries; a discovered numeric width is no longer written to the cross-process cache, since it's keyed by session_id and the same session can be resumed in a differently-sized terminal, which would otherwise serve a stale width for up to terminalWidthCacheTtlSeconds. - Correct the README's claim that the cache is "shared ... across sessions" -- it's deliberately isolated per session_id, which is the point of keying it that way. - Remove context.terminalWidthCacheTtlSeconds: it was write-only dead state (the TTL is already consumed by the getTerminalWidth() call that builds the context). - Add a Terminal Width Cache TTL entry to the TUI's status-line config menu, mirroring the existing Git Cache TTL entry -- the setting was previously only reachable by hand-editing settings.json. - Add integration/wiring tests for the sessionId/ttlSeconds path (previously only unit-tested in isolation) and for the new locking behavior.
…ocks
Around twenty suites replace child_process wholesale with
vi.mock('child_process', ...). That factory form is not file-scoped under
the bun runner, so the replacement stays installed in the module registry
for every file that runs afterwards.
When one of those suites runs before this one, vi.spyOn hands back the
already-installed mock together with its accumulated call history instead
of a fresh spy. The stderr-silencing test iterates every recorded call and
asserts stdio is ['ignore', 'pipe', 'ignore'], so it was inspecting git and
terminal probes from other files, which legitimately use
['pipe', 'pipe', 'ignore'], and failed.
The test passes in isolation and fails in the full suite, which is why CI
reported it as the only failure. Clearing the call history when the spy is
installed scopes every assertion in this file to the calls its own test
made.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
sirmalloc
added a commit
that referenced
this pull request
Sep 17, 2026
Memoize terminal width probes, use native Linux detection, and add a configurable session cache for no-TTY results. Preserve immediate detection of terminal resizes and honor CCSTATUSLINE_WIDTH before the session cache. Integrate the terminal-width TTL control with the shared cache settings editor and cover explicit overrides and three-digit TTL input in tests. PR: #501 Co-authored-by: David André <[email protected]>
sirmalloc
added a commit
that referenced
this pull request
Sep 17, 2026
Memoize terminal width probes, use native Linux detection, and add a configurable session cache for no-TTY results. Preserve immediate detection of terminal resizes and honor CCSTATUSLINE_WIDTH before the session cache. Integrate the terminal-width TTL control with the shared cache settings editor and cover explicit overrides and three-digit TTL input in tests. PR: #501 Co-authored-by: David André <[email protected]>
pcvelz
added a commit
to pcvelz/ccstatusline-usage
that referenced
this pull request
Sep 25, 2026
Upstream: TUI kept off the render path (sirmalloc#575), flex mode default full (sirmalloc#590), usage cache fingerprinted by refresh token (sirmalloc#536), CLAUDE_CONFIG_DIR keychain credential first (sirmalloc#573), llms.txt (sirmalloc#527), faster terminal width probing (sirmalloc#501), git/jj symbol slots (sirmalloc#574), model-scoped 0% quota as real zero (sirmalloc#534), custom-command output cache + timeout (sirmalloc#539), usage-percent widgets on a shared module (sirmalloc#545), hideable reset-timer placeholders (sirmalloc#542), git command timeouts (sirmalloc#559, sirmalloc#585) Hand edits outside conflicts: - src/widgets/shared/usage-percent-widget.ts: compat fix - pass RenderContext to getUsageProgressBarWidth (fork narrow/medium bar widths) and add fork short labels WS:/WO: that the extracted Sonnet/Opus widgets used to render; point the fable-weekly kind at the fork field weeklyFableUsage / resolveWeeklyFableUsageWindow (upstream's fableUsage / resolveFableUsageWindow do not exist in the fork and crashed the render) - .fork-keep-deleted: drop llms.txt (points agents at the upstream package and at docs the fork deletes) Conflict resolutions that deviate from upstream on purpose: - src/types/Settings.ts: keep fork default flexMode full-minus-40 (upstream sirmalloc#590 switched to full) - src/utils/terminal.ts: keep fork tmux $TMUX_PANE width probe, ported to execFileSync; upstream's CCSTATUSLINE_WIDTH override in getTerminalWidth replaces the fork copy - src/ccstatusline.ts: keep getTerminalWidth() without the per-session width cache options (fork invariant); add upstream customCommandCacheTtlSeconds - src/widgets/WeeklyFableUsage.ts (+ test): keep fork widget (Fable:/F: labels, 0% for accounts without Fable, weeklyFableUsage field) instead of upstream's shared-module FableWeeklyUsage - src/utils/__tests__/usage-fetch.test.ts: adopt upstream sirmalloc#534 real-zero semantics on the fork field weeklyFableUsage - docs/test-retirements.md: ledger entries for four tests upstream renamed or replaced (sirmalloc#542, sirmalloc#534) and one duplicate upstream test dropped - src/utils/__tests__/usage-fetch.test.ts: compat - upstream's model-scoped real-zero test expects the fork field weeklyFableUsage - src/widgets/__tests__/WeeklyFableUsage.test.ts: compat - set the shared suite's new required expectedWholePercentTime - src/tui/components/__tests__/ImportPreviewDialog.test.ts: fork deviation - default flexMode is full-minus-40, so the non-default side is full - src/utils/__tests__/usage-fetch.test.ts: drop upstream's duplicate "missing fable window" test that used the upstream-only fableUsage field
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.
Problem
Claude Code spawns the statusline with no TTY, so
probeTerminalWidth()returnsnull. Because every caller reads the width ascontext.terminalWidth ?? getTerminalWidth(), thatnullwas never cached — it re-ran aps/stty/tputancestor walk once per configured line, on every render.Measured on a 6-core Linux box: 122
execveper render, with 6.5s of a 9.9s CPU profile insidespawnSync. Sustained, this was 11.2% of the machine across active sessions.It also produced the wrong answer: the
tput colsfallback reported its no-TTY default of 80 on a 209-column terminal.Fix
nullresult (an explicithasProbedflag — the actual bug)./proc/<pid>/statfor the ancestor chain,readlinkits fds to find a/dev/pts/*, thenTIOCGWINSZviatty.WriteStream. Spawns nothing.execFileSyncinstead ofexecSync+ shell in the remaining fallbacks (no shell process, and non-zero exits propagate instead of being swallowed by an| awkpipeline).session_id, atomictmp+rename, corruption treated as a miss. New settingterminalWidthCacheTtlSeconds(default 5s, mirroringgitCacheTtlSeconds).Existing behaviour is preserved: the
stty/tputfallbacks remain for non-Linux, andCCSTATUSLINE_WIDTHstill wins.Results
execveper rendernodeitself)sysper renderCold and warm cache both cost 1
execve— the native probe spawns nothing, so even a cache miss is free.Tests
32 new/updated tests across
terminal.test.ts(15),terminal-native.test.ts(7),terminal-width-cache.test.ts(10).bun run lintclean.🤖 Generated with Claude Code's help