Repository navigation
[low] perf(claude-status): hold a lock while the status page fetch is in flight - #635
Merged
Merged
Conversation
…ight The claude-status widget only wrote its lock after a failed fetch. When the 300 s cache expired, every render that started before the first fetch finished (other sessions, or renders of the same session) sent its own request(s) to status.claude.com and blocked on them. Write the lock before the fetch, as usage-fetch.ts already does, so concurrent renders serve the expired cache for that one fetch. The lock is still judged by mtime, so an empty lock from an older version keeps working. It now holds contents unique to the fetch, and the success path removes it only while it is still this render's own lock: a concurrent failure lock survives. The lock is removed after the fresh cache is written, not before. Measured with 6 concurrent renders of the built dist per cache expiry, https.request faked with a fixed round trip: - 2 s round trip, renders 200 ms apart: requests per expiry median 4 (2-5) before, 1 after. - 250 ms round trip, simultaneous: median 1 both, max 3 before, 1 after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 30, 2026
Merged
pcvelz
added a commit
to pcvelz/ccstatusline-usage
that referenced
this pull request
Nov 2, 2026
Upstream: weekly limit timers (sirmalloc#768), prompt cache miss widgets (sirmalloc#770), context window size env override (sirmalloc#352, sirmalloc#407), windowed speed fixes (sirmalloc#769, sirmalloc#771), separator/renderer fixes (sirmalloc#773), custom-command styling (sirmalloc#775), clickable cwd links (sirmalloc#744), account email from live credentials (sirmalloc#724), cache timer reported expiry + short toggle (sirmalloc#576, sirmalloc#592), claude-status lock (sirmalloc#635, sirmalloc#772), dedup token metrics (sirmalloc#622, sirmalloc#725, sirmalloc#735). Not adopted: sirmalloc#765 terminal width from COLUMNS with fixed reserve - the fork keeps flexMode/compactThreshold and tmux pane priority. Hand edits outside conflicts: - README.md: kept fork Configure flex separator behavior bullet and older-updates details wrapper; dropped upstream Related Projects additions (claude-eta) - AGENTS.md: kept fork terminal width wording (sirmalloc#765 not adopted) - src/types/FlexMode.ts, src/tui/components/TerminalWidthMenu.tsx, src/tui/components/__tests__/TerminalWidthMenu.test.ts, src/tui/components/TerminalOptionsMenu.tsx, src/tui/components/__tests__/TerminalOptionsMenu.test.ts: restored, fork keeps terminal width settings - src/tui/App.tsx, src/tui/components/index.ts, src/types/index.ts: re-wired terminal width menu and FlexMode export - src/types/StatusJSON.ts: dropped fork duplicate prompt_cache key, upstream schema is a superset - src/utils/terminal.ts: COLUMNS source gated on non-tmux so tmux pane width keeps priority - src/widgets/WeeklyLimitTimer.ts, src/utils/usage-prefetch.ts, src/widgets/__tests__/WeeklyLimitTimer.test.ts: compat fix - fableUsage/fableResetAt -> weeklyFableUsage/weeklyFableResetAt - src/widgets/ApiUsage.tsx: compat fix - Context Bar honors effective context window (override, clamp, 1.0M format); Weekly renders a valid value despite a session error - src/widgets/__tests__/ApiUsage.localModelFallback.test.ts: output tokens no longer counted in context (upstream sirmalloc#730) - src/utils/__tests__/renderer-flex-width.test.ts, src/utils/__tests__/renderer-ansi.test.ts, src/utils/__tests__/gradient.test.ts, src/utils/__tests__/renderer-separator-collapse.test.ts, src/utils/__tests__/config.test.ts, src/tui/__tests__/App.test.ts: fork deviation - fork width model (flexMode, 6-column full reserve, kept settings, Terminal Width menu entry) - src/utils/__tests__/default-colors.test.ts: fork deviation - accept ansi256:/hex: defaults - src/utils/__tests__/usage-prefetch.test.ts: fork deviation - weekly usage comes from the API, fork widget imports from ApiUsage - src/widgets/__tests__/CurrentWorkingDir.test.ts, src/widgets/__tests__/CustomCommand.test.ts: Settings literal gains flexMode/compactThreshold - src/utils/__tests__/context-window.test.ts, src/utils/__tests__/jsonl-metrics.test.ts: restored fork tests upstream dropped - docs/test-retirements.md: ledger for the dev-reload snapshot-path and RemoteControlStatus glyph (sirmalloc#605) tests upstream replaced
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
fetchClaudeServiceStatuswrites its lock only after a failed fetch. When the 300 s cache expires, every render that starts before the first fetch returns sends its own request(s) to status.claude.com. Those are renders from other sessions or later renders of the same session. Each of them also blocks on its request, for up to the 5 s timeout.usage-fetch.tsalready does. Concurrent renders then serve the expired cache while one fetch is in flight. A successful fetch removes the lock only if it still holds the lock it wrote, so a concurrent failure lock survives. This is the ownership rule from [low] fix(usage): only clear the in-flight lock this render wrote #624.Details
writeInFlightLock()writesclaude-status.lockjust beforePromise.all(fetchStatusPagePath(...)). The contents (pid:nowMs:random) are unique to that fetch.isFetchLockActive, renamed fromisFailureLockActive), so the empty lock that older versions write keeps blocking for 30 s. The failure path still refreshes it withwriteFailureLock(), so the 30 s backoff after a failure is unchanged.clearOwnFetchLock()removes the lock, but only if its contents are still ours. Before this change, the unconditionalrmSyncran before the cache write. That left a short window where a render could see neither a lock nor a fresh cache.?fallback for that render. That happens on the very first fetch in a fresh~/.cachewith several sessions running. It also happens when sessions with and without incident history share the cache: while a status-only session's fetch is in flight, a history-enabled session's renders show?for that round trip, because a status-only cache cannot serve the history. That repeats at most once per 300 s refresh and clears as soon as the history session fetches. With a usable cache, it shows the expired value, which is at most one round trip older than what it would have fetched. The usage widgets already behave this way under theirtimeoutin-flight lock.statSyncandwriteFileSync. It is microseconds long, against the round trip it closes. The max of 2 requests seen once under heavy host load comes from that window.usage-fetch.ts. This PR uses the same lock-ownership pattern.Measurements
The herd cannot be seen in a sequential fork/exec bench. Each arm launched 6
node dist/ccstatusline.jsrenders at once against one isolatedHOMEwith a 400 s-oldclaude-status.json. A--requirepreload replacedhttps.requestwith a fixed-delay fake, so nothing reached the real status page. The config wasmodel | claude-status. Old and new dist builds alternated trial by trial within each run.Checks
New cross-process tests in
claude-service-status.test.tsrun real probe processes with a held fake request:bun run lint: clean.bun test: all tests inclaude-service-status.test.tsandClaudeStatus.test.tspass. The host load was about 60 on 6 cores, and at the default 5 s timeout 60 tests failed in unrelated files. With--timeout 60000, 26 still failed:fetchUsageData error handling > preserves root errors…timeout;custom command capturetests;RefreshIntervalMenu,UsageLocaleEditor, …).I re-ran the same files with this PR's two files reverted to
mainon the same host. The same suites failed there (21 failures).bun run build: clean.🤖 Generated with Claude Code