Repository navigation
Conversation
The desktop main process and the renderer each cached the primary environment's bearer token forever. Once the 30-day session expired, the backend closed the socket and every reconnect and HTTP request kept presenting the dead token until the app was restarted. The main process now records when its token expires and exchanges the current bootstrap credential again a minute before then. The renderer only shares an in-flight IPC request, so it always gets the main process's current token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — The PR fixes desktop bearer renewal across expiry and suspend scenarios, but it also changes server bootstrap-credential validation and credential lifetime in authentication code. The targeted tests are useful, yet the authentication policy and session-lifecycle changes require human review. You can add or adjust custom eligibility rules. Learn more. |
|
Taking over this one as maintainer for usage-billing-03. I will rebuild the renewal path against current main and cover recovery with the expiring legacy bootstrap grant as well as rotating bootstrap secrets. Credit to @saphid for identifying both token caches and the original renewal fix. |
|
Closing in favour of #25024, which makes the same change. Thanks for the work here! |
A desktop left running beyond its bearer session lifetime kept returning the expired token from both the main-process and renderer caches. Renewing alone also failed for legacy backends: their fixed bootstrap grant expired after 24 hours, before a 30-day bearer needed renewal.
Main now retains the token with its deadline and exchanges again when 60 seconds or less remain, sharing exchanges under the existing semaphore. Renderer reads share only an in-flight request. Rotating bootstrap tokens are derived at exchange time; a configured legacy credential remains exchangeable for that backend’s lifetime. The server validates that credential directly and issues a fresh short-lived desktop grant, removing the legacy-only seeded-grant cache. A configured rotating secret still takes precedence; fixed launch tokens are rejected in that mode. Persisted pairing-link expiration, proof binding, consumption, revocation, and dev-startup expiration keep their existing path.
Credit to @saphid for identifying the two stale caches and the original renewal fix in #18384.
Verification: 36 focused cases across the four existing desktop/renderer/auth test files pass, consolidated from 39 by combining overlapping cache and renewal fixtures. Exact main fails the three intended regressions (33 pass / 3 fail); the original PR passes 35 / 36 and fails real legacy renewal; the rebuilt implementation passes all 36. The legacy service case exchanges and authenticates a 30-day bearer, advances TestClock 31 days, rejects the expired bearer, then exchanges the same configured credential and authenticates a new session with the same scopes. Desktop tests cover concurrent reads, near-expiry renewal, and a 31-day suspend using the current rotating window. Renderer tests cover shared pending reads and the next renewed token. Another 108 related session, HTTP, linking, authorization, and startup tests pass. Changed-file lint/format and desktop, web, and server typechecks pass.
This is headless service/unit evidence with real auth, session, signing, and in-memory SQLite services; no live Electron reconnect or OS suspend was exercised. Screenshots do not establish token renewal. macOS, Windows/WSL, Electron, iOS, and Android were not run here. A human should verify long-lived Electron HTTP/reconnect behavior and review the authentication policy change. Macroscope’s existing authentication human-review requirement remains outstanding; these checks do not replace it.
Implementation: Codex gpt-6.1-sol (high). Tests: Codex gpt-6-astra (high). Independent review: Claude claude-opus-5-5 and Codex gpt-6-sol (high), via T3 Code.