Skip to content

macOS compatibility: Keychain credential fallback + BSD-portable commands#1

Open
joelrodriguez-creator wants to merge 1 commit intoohugonnot:mainfrom
joelrodriguez-creator:macos-compat-keychain-flock
Open

macOS compatibility: Keychain credential fallback + BSD-portable commands#1
joelrodriguez-creator wants to merge 1 commit intoohugonnot:mainfrom
joelrodriguez-creator:macos-compat-keychain-flock

Conversation

@joelrodriguez-creator
Copy link
Copy Markdown

Summary

Four small changes that let statusline.sh and test_statusline.sh run cleanly on macOS. All fallbacks are Darwin-only or use feature detection — Linux behavior is unchanged.

The problems

On a clean macOS install of Claude Code, the tool installed fine but the session/weekly usage sections silently never rendered. Tracing it down turned up four distinct macOS-compat bugs, any one of which would have been enough to break the usage display:

  1. OAuth token moved to Keychain. Newer Claude Code builds on macOS store the subscription token in the macOS Keychain under service name Claude Code-credentials instead of ~/.claude/.credentials.json. refresh_usage_api returned 1 on every invocation because [ ! -f "$CREDENTIALS_FILE" ] was always true, and the cache was never written.
  2. flock isn't on macOS by default. It's a GNU util-linux tool. ( flock -n 9 || exit 0; refresh_usage_api ) 9>"$LOCK_FILE" errors out on Darwin and skips the refresh entirely (so even if problem macOS compatibility: Keychain credential fallback + BSD-portable commands #1 were fixed, problem #2 would still block the usage display).
  3. sed alternation was non-portable. sed 's/🟢\|🟡\|🔴/⚠/' only works on GNU sed. BSD sed treats \| as literal backslash-pipe in basic regex, so the stale-indicator substitution silently became a no-op.
  4. Test suite used two more GNU-only idioms. touch -d '30 minutes ago' and unquoted wc -l output. Three test failures on macOS before this PR.

The fixes

  • read_oauth_token helper tries $CREDENTIALS_FILE first, then falls back to security find-generic-password -s "Claude Code-credentials" -a "$(whoami)" -w on Darwin. Non-macOS systems without the file still fail fast as they did before.
  • command -v flock check — use the lock when available, call refresh_usage_api directly when not. The cache_age_sec gate above already throttles refresh to once per REFRESH_INTERVAL, so the worst case on a lockless system is 1–2 duplicate API calls per interval for a single user.
  • sed -E 's/(🟢|🟡|🔴)/⚠/' — works on both GNU and BSD sed.
  • touch_minutes_ago helper that tries GNU date -d first, falls back to BSD date -v. count_char pipes through tr -d ' ' to strip BSD wc -l's whitespace padding.

Test results

Environment Before After
macOS 25.3 (Darwin) 53 passed / 3 failed 56 passed / 0 failed
Linux (not re-tested in this PR) unchanged — fallbacks only activate when GNU tools are absent

The Linux happy path is unaffected: if $CREDENTIALS_FILE exists, flock is installed, and GNU tools are available, none of the new branches execute.

Test plan

  • bash test_statusline.sh passes on macOS (56/56)
  • Statusline renders the session quota section live on macOS with Keychain-backed auth (verified with a real OAuth token — ⏳ 🟢 ▓▓░░░░ 29% ↻ 6h33m)
  • Render latency benchmarked: ~60ms cached, ~340ms on refresh (once per REFRESH_INTERVAL)
  • Re-test on Linux to confirm no regression — I don't have a Linux env handy, but happy to wait on CI or another reviewer

Out of scope

  • Windows: Claude Code on Windows probably stores OAuth tokens in Windows Credential Manager (cmdkey / Get-Credential). I didn't implement that branch — no Windows box to verify against. Would be a follow-up PR once someone with a Windows install can test it.
  • Larger test refactor: the test suite could use a proper stub for the refresh function to make individual tests hermetic. Left as-is; the minimal fixes here preserve existing test shape.

🤖 Generated with Claude Code

…ands

Four issues prevented the statusline from working correctly on macOS:

1. **Credentials are in Keychain on newer Claude Code installs, not on disk.**
   `~/.claude/.credentials.json` is only created by older Claude Code
   versions. Current Darwin builds store the subscription OAuth token in
   macOS Keychain under service name `Claude Code-credentials` instead.
   With no fallback, `refresh_usage_api` returned 1 on every invocation and
   the cache was never populated — the session/weekly usage sections
   silently never rendered. Added a `read_oauth_token` helper that tries
   the legacy file path first, then falls back to
   `security find-generic-password` on Darwin.

2. **`flock` is not installed on macOS by default.** It's a GNU/Linux util
   from util-linux and isn't in the default Darwin toolchain. The
   `( flock -n 9 || exit 0; refresh_usage_api ) 9>"$LOCK_FILE"` line
   errored out on Mac and the refresh was skipped for a subtly different
   reason than ohugonnot#1. Added a `command -v flock` check; when absent, call
   `refresh_usage_api` directly. The `cache_age_sec` gate above already
   throttles refresh to one per `REFRESH_INTERVAL`, so the worst case for
   a single-user system is 1–2 duplicate API calls per interval.

3. **`sed 's/A\|B/C/'` alternation isn't portable.** BSD sed (macOS)
   treats `\|` as literal characters in basic regex; only GNU sed
   accepts it as alternation. The stale-indicator substitution silently
   became a no-op on Mac, so the ⚠ never replaced the color dot. Switched
   to `sed -E 's/(A|B|C)/.../ '` which works on both.

4. **Test-suite portability.** Two remaining macOS-compat quirks in
   test_statusline.sh:
   - `touch -d '30 minutes ago'` only works with GNU coreutils; BSD touch
     requires `-t [[CC]YY]MMDDhhmm[.SS]`. Added a `touch_minutes_ago`
     helper that tries GNU `date -d` first, falls back to BSD `date -v`.
   - `wc -l` right-pads output with whitespace on BSD but not on GNU;
     `count_char` returned `"         3"` instead of `"3"` on Mac, failing
     two make_bar assertions. Piped through `tr -d ' '`.

Before: 53 passed / 3 failed on macOS.
After:  56 passed / 0 failed on macOS.
(Linux behavior unchanged — the fallbacks only activate when GNU tools
are absent or the credentials file is missing.)

Verified on macOS 25.3 (Darwin), bash 3.2 + jq 1.7.1. Not tested on
Linux in this PR — but none of the changes alter the happy path where
`$CREDENTIALS_FILE` exists, `flock` is installed, and GNU tools are
available. Windows support (Credential Manager) is out of scope for
this PR; would be a separate branch once someone with a Windows
environment can test it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant