From d73e88f49ff2aae7aebf4a663f9b90d96a981846 Mon Sep 17 00:00:00 2001 From: asdf8675309 <174058705+asdf8675309@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:25:04 -0400 Subject: [PATCH] fix(statusline): move the usage fetch off the render path The location and weather refreshes already run as detached background subshells. The comment above them explains why: a synchronous curl on the render path "killed the perf and got the feature ripped out on 2026-05-08." The usage fetch is the last one still inline. The mutex winner runs security find-generic-password and then a curl with a 3s timeout while the statusline renders. When that endpoint is slow the whole render blocks. Measured with a blackholed endpoint, a render costs 3.36-3.49s against a shipped refreshInterval of 1. Claude Code kills a render that overruns its interval, so those seconds are also a kill window. On macOS the keychain read is the part that hurts: security prompts through readpassphrase(), which opens /dev/tty directly and puts the terminal in canonical no-echo mode. Kill the render mid-call and the terminal can stay that way. Arrow keys emit escape sequences, backspace stops echoing, and typing goes to the password buffer instead of the prompt. Severing stdin with /dev/null; then - echo "$$" > "$USAGE_LOCK/pid" 2>/dev/null # ownership token (verified on release) - # Extract OAuth token — macOS Keychain or Linux credentials file - if [ "$(uname -s)" = "Darwin" ]; then - cred_json=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) - else - cred_json=$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null) - fi - token=$(echo "$cred_json" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) - - if [ -n "$token" ]; then - usage_json=$(curl -s --max-time 3 \ - -H "Authorization: Bearer $token" \ - -H "Content-Type: application/json" \ - -H "anthropic-beta: oauth-2025-04-20" \ - "https://api.anthropic.com/api/oauth/usage" 2>/dev/null) - - # Fail CLOSED: install a new cache ONLY when the body is real JSON - # with a five_hour field. A 429/5xx/HTML body fails this probe, so - # last-known-good is never atomically overwritten with garbage (P5). - if [ -n "$usage_json" ] && echo "$usage_json" | jq -e '.five_hour' >/dev/null 2>&1; then - # Atomic write (P4): temp in the SAME dir (same fs => rename is - # atomic), 0600, and never mv onto a followed symlink (the path - # is predictable in a world-writable dir). Stamp fetched_at (P6). - # mv ONLY if the temp is non-empty (defends jq-exit-0-empty). - _tmp_cache="${USAGE_CACHE}.tmp.$$" - if echo "$usage_json" | jq --argjson now "$_usage_now" '. + {fetched_at:$now}' > "$_tmp_cache" 2>/dev/null && [ -s "$_tmp_cache" ]; then - chmod 600 "$_tmp_cache" 2>/dev/null - [ -L "$USAGE_CACHE" ] && rm -f "$USAGE_CACHE" 2>/dev/null - mv -f "$_tmp_cache" "$USAGE_CACHE" 2>/dev/null && _data_age=0 + # Fetch DETACHED, mirroring the location/weather refresh above. + # The keychain read and the 3s curl must not sit on the render + # path. Claude Code kills a statusline render that overruns its + # refreshInterval, and killing one mid-`security` can strand the + # terminal in the canonical/no-echo mode that call put it in. + # "$USAGE_LOCK/pid" 2>/dev/null # ownership token (verified on release) + # Release on EXIT so a killed fetch cannot leak the mutex, + # and ONLY if we still own it — never nuke another holder's. + trap 'if [ "$(cat "$USAGE_LOCK/pid" 2>/dev/null)" = "$_fetch_pid" ]; then rm -f "$USAGE_LOCK/pid" 2>/dev/null; rmdir "$USAGE_LOCK" 2>/dev/null; fi' EXIT + + # Extract OAuth token — macOS Keychain or Linux credentials file + if [ "$(uname -s)" = "Darwin" ]; then + cred_json=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) + else + cred_json=$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null) + fi + token=$(echo "$cred_json" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) + + if [ -n "$token" ]; then + usage_json=$(curl -s --max-time 3 \ + -H "Authorization: Bearer $token" \ + -H "Content-Type: application/json" \ + -H "anthropic-beta: oauth-2025-04-20" \ + "https://api.anthropic.com/api/oauth/usage" 2>/dev/null) + + # Fail CLOSED: install a new cache ONLY when the body is real JSON + # with a five_hour field. A 429/5xx/HTML body fails this probe, so + # last-known-good is never atomically overwritten with garbage (P5). + if [ -n "$usage_json" ] && echo "$usage_json" | jq -e '.five_hour' >/dev/null 2>&1; then + # Atomic write (P4): temp in the SAME dir (same fs => rename is + # atomic), 0600, and never mv onto a followed symlink (the path + # is predictable in a world-writable dir). Stamp fetched_at (P6). + # mv ONLY if the temp is non-empty (defends jq-exit-0-empty). + _tmp_cache="${USAGE_CACHE}.tmp.${_fetch_pid}" + if echo "$usage_json" | jq --argjson now "$_usage_now" '. + {fetched_at:$now}' > "$_tmp_cache" 2>/dev/null && [ -s "$_tmp_cache" ]; then + chmod 600 "$_tmp_cache" 2>/dev/null + [ -L "$USAGE_CACHE" ] && rm -f "$USAGE_CACHE" 2>/dev/null + mv -f "$_tmp_cache" "$USAGE_CACHE" 2>/dev/null + fi + rm -f "$_tmp_cache" 2>/dev/null fi - rm -f "$_tmp_cache" 2>/dev/null fi - fi - # Release ONLY if we still own the lock — never nuke another holder's. - if [ "$(cat "$USAGE_LOCK/pid" 2>/dev/null)" = "$$" ]; then - rm -f "$USAGE_LOCK/pid" 2>/dev/null; rmdir "$USAGE_LOCK" 2>/dev/null - fi + ) /dev/null 2>&1 & + disown 2>/dev/null || true fi # Losers (mkdir failed): no fetch, no wait — fall through to last-known-good. + # The winner no longer waits either: _data_age keeps its pre-fetch + # value, so this render shows last-known-good with an honest age and + # the fresh data lands on the next tick. The forced-refetch + # bookkeeping below therefore restores _orig_age, which is correct — + # from this render's point of view the attempt has not landed yet. fi # Forced-refetch bookkeeping: a failed forced attempt restores the true # data age (a success already set _data_age=0), so staleness reporting