Skip to content

fix(status-bar): escalate stuck Claude "Refreshing sign-in" after timeout - #10060

Closed
innocarpe wants to merge 3 commits into
stablyai:mainfrom
innocarpe:fix/claude-refreshing-sign-in-escalate
Closed

innocarpe wants to merge 3 commits into
stablyai:mainfrom
innocarpe:fix/claude-refreshing-sign-in-escalate

Conversation

@innocarpe

@innocarpe innocarpe commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

User-facing: Claude usage in the status bar could sit forever on “Refreshing sign-in” even when the terminal Claude session was fine and usage never came back. After this change, the soft “refreshing” state is kept only while repair is in flight; if it stays stuck for 2 minutes, the chip escalates to “Sign-in needs refresh” and the roster offers a re-auth CTA.

Technical: stamp a stable usageMetadata.refreshingSinceMs across continuous Claude stale-token / refreshable-credentials-without-token / delegated-refresh-required failures in applyStalePolicy, then escalate label/message/roster kind once that stamp ages past CLAUDE_REFRESHING_SIGN_IN_ESCALATE_AFTER_MS.

Fixes #8974

Impact / ELI5

Orca tries to refresh Claude’s usage OAuth in the background. Sometimes that refresh never succeeds, but the soft wording still said “we’re refreshing…” forever — like a loading spinner that never ends. Now we give the soft spinner a short time window; if it doesn’t finish, we tell you clearly that you need to re-authenticate for usage tracking (your agent in the terminal may still work).

Root cause

getProviderUsageStatusLabel / getProviderUsageErrorMessage map these Claude failureKinds to permanent soft copy:

  • stale-token
  • refreshable-credentials-without-token
  • delegated-refresh-required

There was no age/timeout escalation. The main fetcher can keep re-emitting those kinds on every poll (updatedAt moves, but the semantic state stays “refreshing”), so the UI never leaves the spinner. Live Claude CLI sessions can remain healthy on a different credential surface while usage tracking is stuck.

Reproduction kit (handoff): docs/bug-reproductions/8974.md on nwparker/bug-repro-handoffs.

Fix

Layer Change
Shared src/shared/claude-refreshing-sign-in.ts — stamp/preserve refreshingSinceMs, escalate predicate (2 min)
Types UsageRateLimitMetadata.refreshingSinceMs?: number
Main applyStalePolicy always runs stampClaudeRefreshingSince so continuous error polls keep the first stamp
UI Status label + tooltip message escalate after timeout; roster row becomes kind: 'sign-in' with re-auth CTA

Behavior matrix

State Label Roster
Refreshing < 2 min Refreshing sign-in error (soft)
Refreshing ≥ 2 min Sign-in needs refresh sign-in (CTA)
Recovered (status: 'ok') normal usage stamp cleared
Live-session deferral Waiting for Claude session unchanged (not escalated)

Evidence

pnpm exec vitest run --config config/vitest.config.ts \
  src/shared/claude-refreshing-sign-in.test.ts \
  src/renderer/src/components/status-bar/repro-8974-refreshing-sign-in-stuck.test.ts \
  src/renderer/src/components/status-bar/usage-roster-row-state.test.ts \
  src/renderer/src/components/status-bar/tooltip.test.ts

Result: 57/57 passed (local).

Key tests:

  • stamp on first refreshing failure; preserve across continuous polls; clear on recovery
  • soft copy while window open; escalated copy + roster CTA after timeout
  • existing tooltip/roster regressions still green

User-regression-tradeoffs

  • Intentional UX change: after ~2 minutes of continuous Claude “refreshing” failures, copy becomes a re-auth prompt instead of an infinite soft spinner. This matches the has_repro handoff suggestion and is better than implying progress that never finishes.
  • Does not change OAuth/credential repair itself — if refresh would have succeeded at 30s, soft copy still applies.
  • Does not touch deferred-by-live-session (“Waiting for Claude session”).
  • Grok/Kimi delegated-CLI refresh paths unchanged.

Checklist notes

  • Cross-platform: pure TS status logic; no OS APIs.
  • SSH/remote: rate-limit state path is shared; stamp lives on usage metadata pushed to the renderer.
  • Agents/integrations: Claude usage UI only; terminal agents unaffected.
  • Performance: O(1) metadata stamp on each stale-policy apply.
  • UI: status-bar label + roster CTA only; no STYLEGUIDE token changes.
  • Security: no secrets; only UI/state timing.
  • i18n: new strings use translate() with English fallbacks (same pattern as nearby status-bar keys).

AI review summary

  • Narrow, handoff-aligned fix for indefinite “Refreshing sign-in”.
  • Shared pure helper keeps main + renderer in sync and is unit-tested without Electron.
  • Escalate window is a constant (2 min) so it can be tuned later without hunting call sites.

ELI5

Claude usage in the status bar could sit on "Refreshing sign-in" forever. After two minutes of no progress it escalates to "Sign-in needs refresh" and offers a re-auth action instead of spinning forever.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: cd40ede8-5c69-448c-835b-886de2b17adb

📥 Commits

Reviewing files that changed from the base of the PR and between 1e50866 and 41d8a95.

📒 Files selected for processing (5)
  • src/main/rate-limits/service/service-result-policy.ts
  • src/renderer/src/components/status-bar/usage-error-copy.ts
  • src/renderer/src/i18n/locales/en.json
  • src/shared/claude-refreshing-sign-in.ts
  • src/shared/rate-limit-types.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change tracks when Claude usage enters a transient sign-in refresh failure. It preserves that timestamp across rate-limit polling and escalates the state after two minutes. Escalated states use “Sign-in needs refresh” status text, re-authentication guidance, and a sign-in roster row. Tests cover timestamp handling, escalation boundaries, renderer output, and repeated polling.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 41d8a

The change provides the intended sign-in recovery guidance after persistent Claude usage-refresh failures without an identified merge-blocking regression.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 5 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: escalating stuck Claude "Refreshing sign-in" status after a timeout.
Description check ✅ Passed The description is detailed and covers the user impact, technical changes, root cause, linked issue, behavior matrix, testing evidence, and compatibility considerations. It does not follow all templat…
Linked Issues check ✅ Passed The PR satisfies the coding objective in #8974. stampClaudeRefreshingSince records the first continuous Claude refreshing failure and preserves it across stale-policy polls. `isClaudeRefreshingSignI…
Out of Scope Changes check ✅ Passed The changes stay within #8974. The new shared helper, usage metadata field, stale-policy integration, Claude status copy, roster state, localization, and regression tests directly support the usage-re…
Full details: Docstring Coverage

Explanation

Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 5 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/shared/claude-refreshing-sign-in.ts (1)

4-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive isClaudeRefreshingFailureKind from CLAUDE_REFRESHING_FAILURE_KINDS instead of duplicating the literals.

The type guard re-hardcodes the same three strings already enumerated in CLAUDE_REFRESHING_FAILURE_KINDS. If the array is ever updated, this function can silently drift out of sync.

♻️ Proposed fix
 export function isClaudeRefreshingFailureKind(
   kind: UsageRateLimitFailureKind | undefined
 ): kind is ClaudeRefreshingFailureKind {
-  return (
-    kind === 'stale-token' ||
-    kind === 'refreshable-credentials-without-token' ||
-    kind === 'delegated-refresh-required'
-  )
+  return kind != null && (CLAUDE_REFRESHING_FAILURE_KINDS as readonly string[]).includes(kind)
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e9187982-6c8e-41b7-95b5-9a9c3e9834df

📥 Commits

Reviewing files that changed from the base of the PR and between 4fce2de and e9e5122.

📒 Files selected for processing (8)
  • src/main/rate-limits/service.ts
  • src/renderer/src/components/status-bar/repro-8974-refreshing-sign-in-stuck.test.ts
  • src/renderer/src/components/status-bar/usage-error-copy.ts
  • src/renderer/src/components/status-bar/usage-roster-row-state.test.ts
  • src/renderer/src/components/status-bar/usage-roster-row-state.ts
  • src/shared/claude-refreshing-sign-in.test.ts
  • src/shared/claude-refreshing-sign-in.ts
  • src/shared/rate-limit-types.ts

Comment thread src/renderer/src/components/status-bar/usage-error-copy.ts
@CannyDuck

Copy link
Copy Markdown

Any plans to merge this PR? Have the same problem on Windows:

{A804CA59-64C7-46BA-AB6E-350072DB358E}

…eout

Claude usage can sit forever on soft "Refreshing sign-in" copy when OAuth
repair keeps failing while the CLI session still works. Stamp the first
refreshing failure time across polls and escalate status-bar/roster copy
to a re-auth CTA after 2 minutes so the chip cannot look stuck indefinitely.

Fixes stablyai#8974
Replace hand-typed auto.* descriptive keys with file-path SHA keys so
localize-renderer-strings stays consistent; catalog the English strings.
@innocarpe
innocarpe force-pushed the fix/claude-refreshing-sign-in-escalate branch from d81a3c3 to 41d8a95 Compare September 17, 2026 13:52
@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (41d8a952f)

Rebased onto the latest upstream/main, ported the refresh timestamp handling through the current split rate-limit service, and addressed the CodeRabbit maintainability nit by deriving the failure-kind check from the shared constant. Focused verification passes: 57 tests, tc:node, oxfmt, and targeted oxlint. This remains limited to the existing Claude usage error path and is ready for review/merge.

@nwparker

Copy link
Copy Markdown
Contributor

Thanks @innocarpe for the work here. Closing as superseded: Orca no longer refreshes Claude tokens itself (#26801), so the stuck 'Refreshing sign-in' state this escalates no longer exists. Appreciate the contribution, and feel free to open a fresh PR against current main if you see anything still missing.

@nwparker nwparker closed this Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: (Does not affect normal use) Claude Code usage status stuck on "Refreshing sign-in" and does not update

4 participants