Linear quick-view caching, OAuth token refresh, and project-scoped autolinks#395
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
📝 WalkthroughWalkthroughThis PR adds proactive and reactive OAuth token auto-refresh for Linear credentials and implements per-project in-memory caching for the Linear issue browser. Token refresh utilities, persistence, and concurrency guards are added and integrated into desktop/headless credential services and the GraphQL client with tests. The browser hydrates state from cache, deduplicates in-flight requests, and receives layout/styling updates. ChangesLinear OAuth token auto-refresh infrastructure
Linear issue browser caching and layout restructuring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
@copilot review but do not make fixes |
There was a problem hiding this comment.
PR Review
Scope: 11 file(s), +792 / −462
Verdict: Minor issues
This PR moves Linear auth to per-project machine credential keys (linear.project.<hash>.*), wipes legacy global/file stores on service construction, aligns headless/CLI readiness checks, and adds stale-while-revalidate caching plus layout polish in LinearIssueBrowser / the quick-view dialog. The credential isolation is a clear win; the main user-facing risk is the intentional no-migration upgrade path and a few browser edge cases around errors and shared env tokens.
🐛 Functionality
[High] Upgrade clears legacy Linear credentials without migration
File: apps/desktop/src/main/services/cto/linearCredentialService.ts:L226-L234 (also L979-L1033)
Issue: On every createLinearCredentialService construction, clearLegacyLinearCredentials() deletes the global machine keys (linear.token.v1, …), project-local linear-token.v1.bin, OAuth config files, and the linear stanza in local.secret.yaml without copying them into the new per-project keys. Anyone upgrading from a build that stored a single shared token will see Linear as disconnected until they reconnect in each project.
Repro: Store a token under legacy linear.token.v1 or .ade/secrets/linear-token.v1.bin, upgrade to this branch, open the project — getStatus().tokenStored is false and CTO/Linear surfaces show disconnected.
Fix: On first construction per project, migrate legacy material into createLinearProjectCredentialKeys(projectRoot) (or gate deletion behind a one-time migration flag), and surface an in-app “Reconnect Linear” banner when legacy material was removed.
[Medium] ADE_LINEAR_* / LINEAR_* env tokens still apply to every project
File: apps/desktop/src/main/services/cto/linearCredentialService.ts:L231-L240
Issue: After scoping machine-store keys per project, readEnvToken() still fills in a token whenever the project-scoped store is empty. A headless or desktop session with ADE_LINEAR_API set will attach the same Linear workspace to all projects, undermining per-project isolation the PR introduces.
Repro: Set ADE_LINEAR_API globally; open project A (no stored token) and project B (no stored token) — both resolve the same env token and workspace.
Fix: Either document env overrides as an explicit global escape hatch, or namespace env lookup (e.g. per-project suffix) and only honor global env when no project-scoped key exists and the operator opts in.
🎨 UI/UX
[Medium] Linear browser error banner can persist after switching projects
File: apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx:L426-L435
Issue: When projectRoot / cacheKey changes, the effect reloads filters, quick view, catalog, and issues from cache but never calls setError(null). A failed load in project A can leave a red error banner visible while project B data renders underneath.
Repro: Open project A’s Linear browser with Linear disconnected or offline to trigger an error; switch to project B with a healthy connection — stale error text remains until another fetch fails or succeeds with force.
Fix: Clear error (and optionally reset loading flags) at the start of the [cacheKey, projectRoot] effect, matching what loadQuickView / loadCatalog do when they start a fetch.
[Low] Issue list can stay stale for up to 90s after external Linear edits
File: apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx:L64-L65, L308-L310, L538-L541
Issue: Search/quick-view/catalog caches treat data as fresh for LINEAR_BROWSER_CACHE_STALE_MS (90s) and skip network refresh unless force is true. Edits made directly in Linear (state, assignee, title) may not appear until the user triggers refresh or waits out the TTL.
Fix: Invalidate the relevant cache entry on refreshKey, window focus, or after lane/resolve actions that mutate Linear issues; or shorten TTL for the issues pane.
⚡ Performance
[Low] Module-level Linear browser cache never evicts old project entries
File: apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx:L113, L251-L254
Issue: linearIssueBrowserCache is a process-lifetime Map keyed by projectRoot::cto:scope. Each distinct project opened in a long session retains quick-view/catalog/search maps (search capped at 16 keys per project, but entries themselves are not removed).
Impact: Slow memory growth when switching among many projects in one Electron session (mostly retained issue arrays and catalog metadata).
Fix: LRU-cap the outer Map (e.g. keep last 4–8 cacheKeys) or delete the entry when projectRoot changes.
Notes
- Security / isolation: Project-scoped keys plus legacy global key deletion fix the prior cross-project token bleed; headless tests for A/B isolation are a good guardrail.
- OAuth: Docs correctly call out ~24h access-token expiry with refresh not wired yet; that limitation predates this diff but is now more visible with OAuth persisted per project.
- Upgrade UX: Commit message and feature docs already say “reconnect once”; consider a dedicated disconnected state in
LinearConnectionPanel/ top-bar quick view so the wipe is not mistaken for a transient network failure. - Positive:
realpathSynccanonicalization inlinearCredentialKeys.tsavoids symlinked/varvs/private/varsplits; CLI readiness now probes the real store instead of removedlinear-token.v1.bin.
Sent by Cursor Automation: BUGBOT in Versic
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ade-cli/src/cli.ts (1)
9065-9090:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPreserve credential-store read failures in the readiness output.
This
catchturns storage errors into “no token present,” soade doctor/ade auth statuswill misdiagnose a broken machine credential store as a missing Linear login. Keep the check non-fatal, but surface the read failure inmessageand/ordetails.Proposed fix
function checkLinearReadiness(projectRoot: string): ReadinessCheck { // Linear tokens are stored under a project-scoped key in the shared machine // credential store (linear.project.<hash>.token.v1). The legacy project-local // linear-token.v1.bin file is no longer written (and is actively removed on // service construction), so probe the real source of truth instead. let storedTokenPresent = false; + let storeReadError: string | null = null; try { const machineStore = new EncryptedFileCredentialStore(); const keys = createLinearProjectCredentialKeys(projectRoot); storedTokenPresent = Boolean(machineStore.getSync(keys.token)?.trim()); - } catch { - storedTokenPresent = false; + } catch (error) { + storeReadError = + error instanceof Error ? error.message : String(error); } const envTokenPresent = Boolean( process.env.ADE_LINEAR_API?.trim() || process.env.LINEAR_API_KEY?.trim() || process.env.ADE_LINEAR_TOKEN?.trim() || process.env.LINEAR_TOKEN?.trim(), ); const ready = storedTokenPresent || envTokenPresent; return { ready, status: ready ? "ready" : "warning", message: ready ? "Linear credentials are present locally." - : "No Linear token was detected in local stores or environment variables.", + : storeReadError + ? "Linear credential storage could not be read." + : "No Linear token was detected in local stores or environment variables.", nextAction: ready ? undefined : "Configure Linear in ADE desktop or set ADE_LINEAR_API/LINEAR_API_KEY for headless mode.", details: { storedTokenPresent, tokenEnvPresent: envTokenPresent, + ...(storeReadError ? { storeReadError } : {}), }, }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/ade-cli/src/cli.ts` around lines 9065 - 9090, The current catch in the credential-read block swallows storage errors and treats them as "no token", so update the logic around EncryptedFileCredentialStore/getSync and createLinearProjectCredentialKeys to capture read failures: instead of only setting storedTokenPresent=false in the catch, set a storedTokenReadError flag (or store the caught Error) and preserve storedTokenPresent=false; then include that flag/error in the returned readiness object (e.g., in details as storedTokenReadError or storedTokenReadErrorMessage) and adjust message/status to surface a read-failure case (for example, when storedTokenReadError is truthy, set status to "warning" and message to indicate a credential-store read error). Ensure the variables referenced are storedTokenPresent, storedTokenReadError, EncryptedFileCredentialStore, and createLinearProjectCredentialKeys so callers can detect the read-failure vs. missing-token cases.
🧹 Nitpick comments (2)
apps/desktop/src/main/services/cto/linearCredentialService.ts (1)
143-148: ⚡ Quick winOrder writes so a partial failure can't leave a token with stale metadata.
writeMachineCredentialrethrows, and the store persists each key independently. If thetokenwrite succeeds but a subsequent metadata write throws, the store ends up with a fresh token alongside staleauthMode/refreshToken/expiresAt(e.g. an OAuth-labeled manual token with a leftover refresh token). Writing the token last on persist — and deleting it first on clear — guarantees the token is only "present" once its metadata is consistent.♻️ Proposed reordering
const persistMachineToken = (record: StoredLinearToken | null): void => { - writeMachineCredential(machineKeys.token, record?.token ?? null); - writeMachineCredential(machineKeys.authMode, record?.authMode ?? null); - writeMachineCredential(machineKeys.refreshToken, record?.refreshToken ?? null); - writeMachineCredential(machineKeys.tokenExpiresAt, record?.expiresAt ?? null); + if (!record) { + // Delete token first: a later failure still leaves no usable token. + writeMachineCredential(machineKeys.token, null); + writeMachineCredential(machineKeys.authMode, null); + writeMachineCredential(machineKeys.refreshToken, null); + writeMachineCredential(machineKeys.tokenExpiresAt, null); + return; + } + // Write metadata first, token last: a partial failure never yields a token with stale metadata. + writeMachineCredential(machineKeys.authMode, record.authMode ?? null); + writeMachineCredential(machineKeys.refreshToken, record.refreshToken ?? null); + writeMachineCredential(machineKeys.tokenExpiresAt, record.expiresAt ?? null); + writeMachineCredential(machineKeys.token, record.token); };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/main/services/cto/linearCredentialService.ts` around lines 143 - 148, persistMachineToken currently writes token before its metadata which can leave a fresh token paired with stale metadata if a subsequent write throws; reorder writes so writeMachineCredential calls for machineKeys.authMode, machineKeys.refreshToken, and machineKeys.tokenExpiresAt happen first and writeMachineCredential(machineKeys.token, ...) is executed last in persistMachineToken, and mirror the opposite behavior in the clear routine (delete token first) so the token is only present when its metadata is consistent; use the existing function names persistMachineToken, writeMachineCredential and the machineKeys identifiers to locate and update the logic.apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx (1)
109-111: 💤 Low valueConsider adding entry limit or cleanup for the outer cache Map.
The
linearIssueBrowserCacheMap accumulates entries keyed byprojectRoot::cto:scopebut never evicts old project entries (only search results within entries are limited). Over extended desktop sessions with multiple project switches, memory could grow unboundedly. Consider adding a max-entry limit with LRU eviction, or cleanup on project/CTO change.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx` around lines 109 - 111, The outer cache linearIssueBrowserCache currently never evicts entries and can grow unbounded; add a bounded-entry policy or explicit cleanup: introduce a MAX_LINEAR_CACHE_ENTRIES constant and on every insertion into linearIssueBrowserCache evict least-recently-used entries (use Map iteration order as LRU by deleting the first key when size > MAX) or provide a cleanup function that removes all entries for a given projectRoot::cto:scope when ctoCacheScopes or nextCtoCacheScope change; update creation/access code for LinearIssueBrowserCacheEntry to move touched keys to the Map tail to maintain LRU ordering and ensure ctoCacheScopes/nextCtoCacheScope updates trigger removal of stale keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/ade-cli/src/cli.ts`:
- Around line 9065-9090: The current catch in the credential-read block swallows
storage errors and treats them as "no token", so update the logic around
EncryptedFileCredentialStore/getSync and createLinearProjectCredentialKeys to
capture read failures: instead of only setting storedTokenPresent=false in the
catch, set a storedTokenReadError flag (or store the caught Error) and preserve
storedTokenPresent=false; then include that flag/error in the returned readiness
object (e.g., in details as storedTokenReadError or storedTokenReadErrorMessage)
and adjust message/status to surface a read-failure case (for example, when
storedTokenReadError is truthy, set status to "warning" and message to indicate
a credential-store read error). Ensure the variables referenced are
storedTokenPresent, storedTokenReadError, EncryptedFileCredentialStore, and
createLinearProjectCredentialKeys so callers can detect the read-failure vs.
missing-token cases.
---
Nitpick comments:
In `@apps/desktop/src/main/services/cto/linearCredentialService.ts`:
- Around line 143-148: persistMachineToken currently writes token before its
metadata which can leave a fresh token paired with stale metadata if a
subsequent write throws; reorder writes so writeMachineCredential calls for
machineKeys.authMode, machineKeys.refreshToken, and machineKeys.tokenExpiresAt
happen first and writeMachineCredential(machineKeys.token, ...) is executed last
in persistMachineToken, and mirror the opposite behavior in the clear routine
(delete token first) so the token is only present when its metadata is
consistent; use the existing function names persistMachineToken,
writeMachineCredential and the machineKeys identifiers to locate and update the
logic.
In `@apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx`:
- Around line 109-111: The outer cache linearIssueBrowserCache currently never
evicts entries and can grow unbounded; add a bounded-entry policy or explicit
cleanup: introduce a MAX_LINEAR_CACHE_ENTRIES constant and on every insertion
into linearIssueBrowserCache evict least-recently-used entries (use Map
iteration order as LRU by deleting the first key when size > MAX) or provide a
cleanup function that removes all entries for a given projectRoot::cto:scope
when ctoCacheScopes or nextCtoCacheScope change; update creation/access code for
LinearIssueBrowserCacheEntry to move touched keys to the Map tail to maintain
LRU ordering and ensure ctoCacheScopes/nextCtoCacheScope updates trigger removal
of stale keys.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4cce70e2-38d7-45f4-aeea-bd532513eca4
⛔ Files ignored due to path filters (3)
docs/features/cto/linear-integration.mdis excluded by!docs/**docs/features/linear-integration/README.mdis excluded by!docs/**docs/features/onboarding-and-settings/README.mdis excluded by!docs/**
📒 Files selected for processing (8)
apps/ade-cli/src/cli.tsapps/ade-cli/src/headlessLinearServices.test.tsapps/ade-cli/src/headlessLinearServices.tsapps/desktop/src/main/services/cto/linearAuth.test.tsapps/desktop/src/main/services/cto/linearCredentialKeys.tsapps/desktop/src/main/services/cto/linearCredentialService.tsapps/desktop/src/renderer/components/app/LinearIssueBrowser.tsxapps/desktop/src/renderer/components/app/LinearQuickViewButton.tsx
Add a per-project stale-while-revalidate cache to the Linear issue browser so the quick-view popover opens instantly and survives reopen. Quick view, filter catalog, and issue search each show cached data immediately, dedupe in-flight requests, and guard against stale responses with per-loader request-id checks (incl. loadCatalog, which previously lacked one — this prevents a stale catalog overwrite when the active project changes while the overlay is open). Cache is bounded (90s freshness, 16 searches per scope). Also enlarge the quick-view modal and refresh the issue detail pane (card header, metadata pills, branch box, two-column info grid, sticky action dock). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cd3e94a to
0f55a82
Compare
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…ject Linear OAuth access tokens expire ~24h after sign-in and Linear issues a rotating refresh token, but ADE never refreshed — so OAuth connections (the primary sign-in path) silently broke after a day. Add automatic refresh: - linearTokenRefresh.ts: shared grant_type=refresh_token exchange + an expiry/near-expiry check. - linearCredentialService.ensureFreshToken() (desktop) and the headless ade-serve credential service: refresh when at/near expiry, rotate the refresh token, clear the connection on invalid_grant (prompt reconnect), keep the token on transient failures. Concurrency-deduped. - linearClient: refresh proactively before each GraphQL request and the quick-view SDK call, and reactively once on a 401, re-reading the token per attempt. Also fix the Settings > Linear "GitHub reference links" (autolink) panel: - Reload connection, repo, and team keys when the active project changes, so the generated `gh repo autolink` commands target the current project's repo and Linear workspace instead of a stale one. - Clarify the confusing copy: explain what autolinks do, that they apply to this project's repo, and that Create configures it automatically while the shown gh command is the manual equivalent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/desktop/src/renderer/components/settings/LinearSection.tsx`:
- Around line 204-206: The async loadGithubAutolinks flow can set
githubRepo/githubAutolinks from stale responses when switching projects quickly;
add a cancellation/request-id guard: create a requestId (useRef counter or local
const within the useEffect) and pass it into loadGithubAutolinks or have
loadGithubAutolinks capture it; inside detectRepo() and listRepoAutolinks()
response handlers only call setGithubRepo/setGithubAutolinks if the current
requestId matches (or if an AbortSignal is not aborted), and increment/abort the
previous request on cleanup of the effect so older promises are ignored—update
the useEffect and loadGithubAutolinks/detectRepo/listRepoAutolinks logic to
perform these checks using the request-id or AbortController to prevent stale
updates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f8b298b5-3fac-42bb-a320-694736a63975
⛔ Files ignored due to path filters (1)
docs/features/cto/linear-integration.mdis excluded by!docs/**
📒 Files selected for processing (9)
apps/ade-cli/src/headlessLinearServices.tsapps/desktop/src/main/services/cto/linearAuth.test.tsapps/desktop/src/main/services/cto/linearClient.tsapps/desktop/src/main/services/cto/linearCredentialService.tsapps/desktop/src/main/services/cto/linearTokenRefresh.test.tsapps/desktop/src/main/services/cto/linearTokenRefresh.tsapps/desktop/src/renderer/components/app/LinearIssueBrowser.tsxapps/desktop/src/renderer/components/app/LinearQuickViewButton.tsxapps/desktop/src/renderer/components/settings/LinearSection.tsx
✅ Files skipped from review due to trivial changes (1)
- apps/desktop/src/renderer/components/app/LinearQuickViewButton.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/renderer/components/app/LinearIssueBrowser.tsx
loadGithubAutolinks lacked the request-id guard that loadStatus uses, so a rapid active-project switch could let an older detectRepo()/listRepoAutolinks() response land last and repopulate the repo/autolinks for the previous project — making the displayed repo and generated `gh repo autolink` commands wrong for the active project. Add an autolinksRequestIdRef and bail out of the stale setState calls, mirroring the existing loadStatus pattern. (CodeRabbit review.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The refresh helper flagged invalidGrant on any 400/401 or invalid_request, which would clear (persistToken(null)) a still-valid refresh token when the failure was actually a client-config error (invalid_client, also 400/401) or a malformed request — forcing a needless full reconnect. Per RFC 6749 §5.2, only error === "invalid_grant" means the refresh token itself is dead; every other 4xx/5xx/network failure is kept and retried. Add tests covering invalid_client (401) and invalid_request (400) -> invalidGrant false. (Greptile review.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |


Summary
Three Linear improvements across the issue browser and connection layer.
1. Quick-view caching —
LinearIssueBrowser.tsx,LinearQuickViewButton.tsxPer-project stale-while-revalidate cache so the top-bar quick view opens instantly and survives reopen. Quick view, filter catalog, and issue search each render cached data immediately, dedupe in-flight requests, and guard against stale responses with per-loader request-id checks — including
loadCatalog, which previously had none. Larger modal + redesigned issue-detail pane.2. OAuth token refresh —
linearTokenRefresh.ts, credential services,linearClient.tsLinear OAuth access tokens expire ~24h after sign-in and Linear issues a rotating refresh token, but ADE never refreshed — so OAuth connections (the primary sign-in path) silently broke after a day. Now:
ensureFreshToken()on both the desktop and headless (ade serve) credential services refreshes viagrant_type=refresh_tokenwhen at/near expiry, rotates the refresh token, clears the connection oninvalid_grant(prompts reconnect), and keeps the token on transient failures. Concurrency-deduped.linearClientrefreshes proactively before each GraphQL request + the quick-view SDK call, and reactively once on a 401 (re-reading the token per attempt).expires_in: 86399; the refresh-token system has been mandatory since 2026-04-01).3. Project-scoped autolink setup —
LinearSection.tsxSettings → Linear "GitHub reference links" now reloads connection / repo / team-keys when the active project changes, so the generated
gh repo autolinkcommands target the current project's repo + Linear workspace (previously stale on project switch). Clearer copy: explains what autolinks do, that they apply to this project's repo, and that Create configures it automatically while the shownghcommand is the manual equivalent.Verification
linearTokenRefreshhelper (7) +linearAuthOAuth-refresh cases (4: near-expiry rotation, no-refresh for manual/fresh, invalid_grant→clear, transient→keep).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Greptile Summary
This PR fixes a real-world session expiry bug (OAuth tokens silently dying after ~24 h), adds stale-while-revalidate caching to the Linear issue browser, and makes the Settings → Linear autolink section project-aware. All three changes are well-scoped and correctly guarded.
linearTokenRefresh.ts,linearCredentialService.ts,linearClient.ts): sharedrefreshLinearOAuthAccessTokenhelper correctly narrowsinvalidGranttoerror === "invalid_grant"only (per RFC 6749 §5.2); proactive + reactive refresh paths inlinearClientdeduplicate in-flight refreshes viarefreshInFlightand prevent infinite loops with thedidAuthRefreshflag.LinearIssueBrowser.tsx): module-levellinearIssueBrowserCacheimplements stale-while-revalidate for quick-view, catalog, and search results with per-loader request-ID guards and in-flight deduplication; state is pre-seeded from cache on mount.LinearSection.tsx): subscribes touseAppStoreforprojectRootand wires it into both load effects, with anautolinksRequestIdRefguard preventing stale detectRepo/listRepoAutolinks responses from populating the wrong project.Confidence Score: 5/5
The PR is safe to merge. The token-refresh logic correctly handles all error cases (invalid_grant vs. transient), the cache uses request-ID guards to prevent stale data from crossing project boundaries, and the existing test suite was extended to cover the new refresh paths.
All three change areas are logically sound: the invalidGrant detection was narrowed to only error === invalid_grant (fixing the previously flagged overly-broad 400/401 check), the React caching is properly scoped per project root and CTO bridge instance, and the project-switch reload in LinearSection is guarded against in-flight race conditions. No data-loss or incorrect-auth paths were found in the new code.
No files require special attention. The headless credential service's ensureFreshToken does not support fetchImpl injection (flagged as a suggestion), but this does not affect correctness in production.
Important Files Changed
Sequence Diagram
sequenceDiagram participant R as Renderer participant LC as linearClient participant CS as CredentialService participant TR as linearTokenRefresh participant LA as Linear API R->>LC: getQuickView() / request() LC->>CS: ensureFreshToken() [proactive] CS-->>CS: check expiresAt vs now - 2min buffer alt token near expiry CS->>TR: refreshLinearOAuthAccessToken() TR->>LA: POST /oauth/token (refresh_token) LA-->>TR: "{access_token, refresh_token, expires_in}" TR-->>CS: "{ok:true, accessToken, refreshToken, expiresAt}" CS-->>CS: persistToken() + invalidateCache() else token fresh or manual CS-->>LC: (no-op) end LC->>LA: GraphQL / SDK request alt 401 received LA-->>LC: 401 Unauthorized LC->>CS: "ensureFreshToken({force:true}) [reactive, once]" alt invalid_grant CS-->>CS: persistToken(null) — clear connection LC-->>R: throw token missing error else transient failure CS-->>CS: keep existing token LC->>LA: retry with same token else success CS-->>CS: persistToken(newToken) LC->>LA: retry with new token LA-->>LC: 200 OK LC-->>R: data end else success LA-->>LC: 200 OK LC-->>R: data endPrompt To Fix All With AI
Reviews (5): Last reviewed commit: "Only treat invalid_grant as a dead Linea..." | Re-trigger Greptile