Skip to content

Fix expired OAuth token and keychain lookup issues#3

Open
inspiretelapps wants to merge 1 commit intorobinebers:mainfrom
inspiretelapps:fix/token-refresh-and-keychain-lookup
Open

Fix expired OAuth token and keychain lookup issues#3
inspiretelapps wants to merge 1 commit intorobinebers:mainfrom
inspiretelapps:fix/token-refresh-and-keychain-lookup

Conversation

@inspiretelapps
Copy link
Copy Markdown

@inspiretelapps inspiretelapps commented Feb 1, 2026

Summary

The statusline script silently fails to show API usage limits when OAuth tokens expire. This PR fixes three root causes:

  • Keychain lookup ambiguity: On systems with multiple Claude Code-credentials keychain entries (e.g. a stale root entry alongside the current user entry), security find-generic-password returns the wrong one. Now resolves by current username (whoami) first, with fallback.
  • No token refresh: OAuth tokens expire after ~8 hours but the script had no refresh logic. Added automatic refresh via platform.claude.com/v1/oauth/token with a 1-hour file-based token cache to avoid unnecessary refresh calls on every statusline update.
  • API response format change: seven_day_opus is now null for non-Opus users; seven_day_sonnet is returned instead. The script now dynamically picks whichever model-specific limit is available and labels it accordingly.
  • Integer comparison errors: color_pct threw integer expression expected when the API call failed and returned empty values. Added guards for empty/non-numeric inputs.

Test plan

  • Verify limits display correctly with a valid OAuth token
  • Verify token auto-refreshes after expiry (wait 8+ hours or manually expire the keychain token)
  • Verify correct model label shown (Opus vs Sonnet) depending on subscription
  • Verify no errors on stderr when API is unreachable

🤖 Generated with Claude Code


Summary by cubic

Fixes silent failures in the statusline by refreshing expired OAuth tokens, resolving keychain lookup ambiguity, and adapting to the new API fields. Limits now display reliably with correct model labels and no integer errors.

  • Bug Fixes
    • Prefer current user’s Keychain entry when reading "Claude Code-credentials", with a fallback if missing.
    • Add automatic OAuth token refresh (platform.claude.com) with a 1-hour file cache; updates Keychain with new tokens.
    • Handle API format change by falling back from seven_day_opus to seven_day_sonnet and label the model limit accordingly.
    • Guard color_pct against empty/non-numeric values to prevent "integer expression expected" errors.

Written for commit e6851d9. Summary will update on new commits.

The script was silently failing to show API usage limits because:

1. On systems with multiple "Claude Code-credentials" keychain entries
   (e.g. an old "root" entry and a current user entry), macOS returned
   the stale one. Now looks up by current username first.

2. OAuth tokens expire after 8 hours but the script had no refresh
   logic. Added automatic token refresh via platform.claude.com with
   a 1-hour token cache to avoid unnecessary refresh calls.

3. The API response format changed — seven_day_opus is now null for
   non-Opus users, with seven_day_sonnet returned instead. The script
   now dynamically detects which model-specific limit is available.

4. The color_pct function threw "integer expression expected" errors
   when receiving empty or non-numeric values from failed API calls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@macroscopeapp
Copy link
Copy Markdown

macroscopeapp bot commented Feb 1, 2026

Fix OAuth token expiry and keychain lookup in statusline-command.sh by adding cached token refresh via get_access_token and labeling Opus/Sonnet utilization with a 7‑day percentage

Introduce get_access_token to read, cache, and refresh OAuth tokens (Keychain + POST https://platform.claude.com/v1/oauth/token); route usage requests through the refreshed token; add dynamic parsing for .seven_day_opus.utilization and .seven_day_sonnet.utilization with model label in output; harden percentage coloring for empty or non-numeric values.

📍Where to Start

Start with get_access_token in statusline-command.sh, then review its use in fetch_usage and the output changes in the main print logic.


📊 Macroscope summarized e6851d9. 1 file reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted. View details

new_token=$(echo "$refresh_result" | jq -r '.access_token // empty' 2>/dev/null)
if [ -n "$new_token" ]; then
# Cache the refreshed token
echo "$refresh_result" > "$TOKEN_CACHE_FILE"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical

statusline-command.sh:59

OAuth tokens are written to /tmp/claude-oauth-token-cache.json with default (world-readable) permissions, allowing any local user to steal credentials. Consider restricting permissions with chmod 600 after writing or using a user-specific directory like ~/.cache/.

-      echo "$refresh_result" > "$TOKEN_CACHE_FILE"
+      echo "$refresh_result" > "$TOKEN_CACHE_FILE"
+      chmod 600 "$TOKEN_CACHE_FILE"

🚀 Want me to fix this? Reply ex: "fix it for me".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix it for me

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 1, 2026

Greptile Overview

Greptile Summary

Fixes OAuth token expiration by adding automatic refresh logic with 1-hour caching, resolves keychain ambiguity by filtering on current username, and adapts to API changes where model limits now return seven_day_sonnet instead of seven_day_opus for non-Opus users.

Key improvements:

  • OAuth tokens now auto-refresh when expired, preventing silent failures
  • Keychain lookup prioritizes current user's account to avoid stale entries
  • Dynamic model limit detection handles both Opus and Sonnet subscriptions
  • Integer comparison guards prevent errors when API calls fail

Minor portability notes:

  • Uses stat -f (macOS-specific); consider adding Linux support with stat -c if cross-platform use is intended
  • Keychain delete/add operations aren't atomic; very rare race condition possible with concurrent statusline calls

Confidence Score: 4/5

  • Safe to merge - fixes critical token expiration bug with sound refresh logic
  • Robust implementation addresses real production issues; minor portability and race condition concerns don't affect core functionality on target platform (macOS)
  • No files require special attention

Important Files Changed

Filename Overview
statusline-command.sh Adds OAuth token refresh logic, keychain account disambiguation, and dynamic model limit detection; fixes integer comparison guards

Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

get_access_token() {
# Check token cache first (refreshed tokens are cached here)
if [ -f "$TOKEN_CACHE_FILE" ]; then
token_cache_age=$(($(date +%s) - $(stat -f %m "$TOKEN_CACHE_FILE" 2>/dev/null || echo 0)))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stat -f is BSD/macOS-only; add Linux compatibility with stat -c

Prompt To Fix With AI
This is a comment left during a code review.
Path: statusline-command.sh
Line: 21:21

Comment:
`stat -f` is BSD/macOS-only; add Linux compatibility with `stat -c`

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +68 to +69
security delete-generic-password -s "Claude Code-credentials" -a "$acct" >/dev/null 2>&1
security add-generic-password -s "Claude Code-credentials" -a "$acct" -w "$updated_creds" 2>/dev/null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: keychain delete/add isn't atomic; concurrent statusline calls could corrupt credentials

Prompt To Fix With AI
This is a comment left during a code review.
Path: statusline-command.sh
Line: 68:69

Comment:
Race condition: keychain delete/add isn't atomic; concurrent statusline calls could corrupt credentials

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Feb 1, 2026

Additional Comments (1)

statusline-command.sh
stat -f is BSD/macOS-only; add Linux compatibility with stat -c

Prompt To Fix With AI
This is a comment left during a code review.
Path: statusline-command.sh
Line: 90:90

Comment:
`stat -f` is BSD/macOS-only; add Linux compatibility with `stat -c`

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="statusline-command.sh">

<violation number="1" location="statusline-command.sh:59">
P1: OAuth tokens written to `/tmp` are world-readable by default. This exposes the access token to other users on multi-user systems. Set restrictive permissions when writing sensitive credentials.</violation>

<violation number="2" location="statusline-command.sh:166">
P2: When `pct` is empty, this outputs just `%` with no number. The empty check should substitute a default value to display `0%` instead.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

new_token=$(echo "$refresh_result" | jq -r '.access_token // empty' 2>/dev/null)
if [ -n "$new_token" ]; then
# Cache the refreshed token
echo "$refresh_result" > "$TOKEN_CACHE_FILE"
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: OAuth tokens written to /tmp are world-readable by default. This exposes the access token to other users on multi-user systems. Set restrictive permissions when writing sensitive credentials.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At statusline-command.sh, line 59:

<comment>OAuth tokens written to `/tmp` are world-readable by default. This exposes the access token to other users on multi-user systems. Set restrictive permissions when writing sensitive credentials.</comment>

<file context>
@@ -12,9 +12,70 @@ model=$(echo "$input" | jq -r '.model.display_name')
+    new_token=$(echo "$refresh_result" | jq -r '.access_token // empty' 2>/dev/null)
+    if [ -n "$new_token" ]; then
+      # Cache the refreshed token
+      echo "$refresh_result" > "$TOKEN_CACHE_FILE"
+
+      # Update Keychain with new credentials
</file context>
Suggested change
echo "$refresh_result" > "$TOKEN_CACHE_FILE"
echo "$refresh_result" > "$TOKEN_CACHE_FILE"
chmod 600 "$TOKEN_CACHE_FILE"
Fix with Cubic

local pct=$1
if [ "$pct" -ge 90 ]; then
if [ -z "$pct" ] || [ "$pct" = "0" ]; then
echo "\033[32m${pct}%\033[0m" # green
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When pct is empty, this outputs just % with no number. The empty check should substitute a default value to display 0% instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At statusline-command.sh, line 166:

<comment>When `pct` is empty, this outputs just `%` with no number. The empty check should substitute a default value to display `0%` instead.</comment>

<file context>
@@ -91,9 +162,11 @@ fi
   local pct=$1
-  if [ "$pct" -ge 90 ]; then
+  if [ -z "$pct" ] || [ "$pct" = "0" ]; then
+    echo "\033[32m${pct}%\033[0m"  # green
+  elif [ "$pct" -ge 90 ] 2>/dev/null; then
     echo "\033[31m${pct}%\033[0m"  # red
</file context>
Suggested change
echo "\033[32m${pct}%\033[0m" # green
echo "\033[32m${pct:-0}%\033[0m" # green
Fix with Cubic

@robinebers
Copy link
Copy Markdown
Owner

Thank you, I think it might be a better idea to turn this into a Claude Skill now that they're so powerful to keep this dynamic! Will consider and merge your changes then. 🙏

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.

2 participants