Skip to content

fix: tighter auth error handling + restrict X-GitHub-Token forwarding#490

Merged
rainxchzed merged 9 commits intomainfrom
fix/auth-resilience-and-token-scope
May 3, 2026
Merged

fix: tighter auth error handling + restrict X-GitHub-Token forwarding#490
rainxchzed merged 9 commits intomainfrom
fix/auth-resilience-and-token-scope

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 3, 2026

Stack of three fixes for the "Retry failed to load releases: HTTP 403" + spurious "session expired" prompts the user reported. Backend-side investigation in flight; everything here is safe to ship regardless of what backend confirms.

A — Stop leaking the user's PAT to non-search backend endpoints

BackendApiClient was sending the user's GitHub token via X-GitHub-Token on /v1/repo, /v1/releases, /v1/readme, /v1/user, and /v1/auth/exchange, even though the project notes explicitly scope the header to /v1/search and /v1/search/explore. The other endpoints don't accept it server-side, so we were leaking the PAT for no upside. This change strips it from every endpoint the spec doesn't list and adds a comment so it doesn't drift again.

B — Don't sign the user out on a single 401

AuthenticationStateImpl.notifySessionExpired used to clear the token and emit the session-expired dialog on the very first 401 reported by UnauthorizedInterceptor. That nukes a still-valid token whenever the 401 comes from:

  • a stale request that fired before sign-in completed,
  • an endpoint that requires extra OAuth scope the token doesn't carry,
  • a transient GitHub edge / proxy hiccup.

Now it tracks consecutive 401s under the same token within a 60-second window. Sign-out only fires on the second hit. Different token (re-auth landed) or a long gap resets the streak. Logs the deferred-vs-cleared decision so we can audit if it ever feels too lenient.

C — Friendlier message when retrying releases fails

DetailsViewModel.retryReleases used to bury the cause in a logger.warn and leave the UI on a generic "failed" card. The user sees "HTTP 403" in logs which reads like a real error. Now we keep the diagnostic log (cause stays for support), and emit a DetailsEvent.OnMessage with the new releases_unavailable_temporarily string so the snackbar tells the user what's actually happening: the list will come back on its own.

Test plan

  • Sign in successfully. Open a repo with many releases. Hit "Retry releases" once or twice — confirm no session-expired dialog appears even if intermittent failures occur.
  • Provoke a real 401 (revoke the token from GitHub Settings → Personal access tokens) and hit a tracked-app endpoint twice. Session-expired dialog should fire on the second 401 within the same minute, not the first.
  • In Details, when retry releases fails, the snackbar reads "Releases are temporarily unavailable. Please try again in a bit." — not the raw HTTP 403 line.
  • Sniffing requests from the client to /v1/repo, /v1/releases, /v1/readme, /v1/user, /v1/auth/exchange shows no X-GitHub-Token header. /v1/search and /v1/search/explore still send it.

Summary by CodeRabbit

  • Bug Fixes

    • Prevents premature sign-out by debouncing repeated authorization failures for the same token and requiring consecutive failures before clearing session.
  • Improvements

    • Successful requests now reset failure tracking to avoid transient logout.
    • Shows a user-facing message when release data is temporarily unavailable.
  • Documentation

    • Clarified passthrough token behavior and how 401s are handled in docs.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Debounce token-specific 401s before clearing credentials, add success notifications to reset debounce, extract bearer tokens in the network interceptor, remove injected CoroutineScope from client wiring, add a user-facing "releases temporarily unavailable" string and surface it via DetailsViewModel, and update docs describing passthrough header behavior.

Changes

Token-aware 401 Debouncing

Layer / File(s) Summary
Interface Contract
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/AuthenticationState.kt
notifySessionExpired() changed to notifySessionExpired(tokenKey: String?) and notifyRequestSucceeded(tokenKey: String?) added.
Debouncing Logic
core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt
Implements token-keyed debouncing: snapshots failing token and timestamp, counts consecutive failures within 60s, requires 2 consecutive failures before clearing token and emitting sessionExpiredEvent, verifies token hasn't rotated before clearing, adds notifyRequestSucceeded(tokenKey) and helper reset logic and constants.
Token Extraction & Integration
core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt
Extracts bearer token from Authorization header and calls notifySessionExpired(tokenKey) on 401, notifyRequestSucceeded(tokenKey) on non-401; removes coroutine scope usage and scope config from the interceptor.
HttpClient Wiring
core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/HttpClientFactory.kt, core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/GitHubClientProvider.kt
Removed scope parameter from createGitHubHttpClient and stopped passing provider scope into clients; interceptor installation now depends only on authenticationState != null.
Documentation
CLAUDE.md, AGENTS.md
Expanded X-GitHub-Token passthrough documentation to additional /v1/* passthrough endpoints and clarified 401 semantics with debounced clearing behavior.

Release Unavailability Messaging

Layer / File(s) Summary
String Resource
core/presentation/src/commonMain/composeResources/values/strings.xml
Adds releases_unavailable_temporarily string: "Releases are temporarily unavailable. Please try again in a bit."
Error Handling & UI Events
feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
retryReleases() now emits DetailsEvent.OnMessage with the new string on non-cancellation errors and sets releasesLoadFailed = true in state.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Interceptor as UnauthorizedInterceptor
    participant State as AuthenticationStateImpl
    participant Store as TokenStore
    participant UI as ViewModel

    Client->>Interceptor: HTTP request (Authorization: Bearer ...)
    Interceptor->>Interceptor: extractBearerToken() -> tokenKey

    alt 401 response
        Interceptor->>State: notifySessionExpired(tokenKey)
        activate State
        State->>State: if no snapshot -> snapshot tokenKey + timestamp
        State->>State: else if same tokenKey within 60s -> increment counter
        alt counter >= 2
            State->>Store: verify current token == failing tokenKey
            alt token matches
                State->>Store: clear token
                State->>UI: emit sessionExpiredEvent
            else token rotated
                State->>State: reset counters
            end
        end
        deactivate State
    else non-401 response
        Interceptor->>State: notifyRequestSucceeded(tokenKey)
        activate State
        State->>State: if tokenKey matches snapshot -> reset counters
        deactivate State
    end

    Interceptor->>Client: return response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I nibbled tokens, timed each hop,
Two knocks before we let them drop.
When releases hide and servers sigh,
"Try again in a bit"—I cry.
Hops and hops, but gentle we stop.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: tighter auth error handling (requiring consecutive 401s) and restricting X-GitHub-Token forwarding to specific endpoints.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auth-resilience-and-token-scope

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt (1)

31-33: ⚡ Quick win

Prefix the new private state fields with _ for consistency.

These mutable private state properties don't follow the repo's underscore-prefix convention. Renaming them to _failingTokenSnapshot, _firstFailureAtMillis, and _consecutiveFailures would match the rest of the Kotlin state surface.

As per coding guidelines, "Use private underscore prefix for private state properties: _state, _events, _mutableState".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt`
around lines 31 - 33, Rename the three private mutable fields in
AuthenticationStateImpl: change failingTokenSnapshot → _failingTokenSnapshot,
firstFailureAtMillis → _firstFailureAtMillis, and consecutiveFailures →
_consecutiveFailures; update every reference/usages (reads, writes,
constructors, and any copy/serialization code) to use the new names so the class
follows the private underscore-prefix convention (look for occurrences of
failingTokenSnapshot, firstFailureAtMillis, and consecutiveFailures to update).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt`:
- Around line 46-66: notifySessionExpired() currently reads
tokenStore.currentToken() inside the coroutine which misattributes late 401s to
the current token and never resets on successful responses; change its API to
accept the failing request's token snapshot (pass token.accessToken from the
request context where notifySessionExpired() is invoked), update the logic in
AuthenticationStateImpl to use that passed tokenKey instead of calling
tokenStore.currentToken(), clear/reset the streak only when a non-401 response
is observed for the same tokenKey, and rename the private mutable fields to use
the underscore-prefix convention (_failingTokenSnapshot, _firstFailureAtMillis,
_consecutiveFailures) while preserving existing behavior otherwise.

---

Nitpick comments:
In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt`:
- Around line 31-33: Rename the three private mutable fields in
AuthenticationStateImpl: change failingTokenSnapshot → _failingTokenSnapshot,
firstFailureAtMillis → _firstFailureAtMillis, and consecutiveFailures →
_consecutiveFailures; update every reference/usages (reads, writes,
constructors, and any copy/serialization code) to use the new names so the class
follows the private underscore-prefix convention (look for occurrences of
failingTokenSnapshot, firstFailureAtMillis, and consecutiveFailures to update).
🪄 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: 65d780ce-079a-4501-b907-2110628e3763

📥 Commits

Reviewing files that changed from the base of the PR and between 44317fb and 28fcb98.

📒 Files selected for processing (4)
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Line 162: Clarify that the parenthetical "(only on the lazy-fetch DB-miss
path)" describes backend behavior, not client-side logic: update the sentence
mentioning `/v1/repo` to state that the client (BackendApiClient.getRepo /
TokenStore.currentToken) always sends the `X-GitHub-Token` header on requests,
and that the backend decides (on a lazy-fetch DB miss) whether to forward that
header to GitHub; make the client/backend boundary explicit so readers know
getRepo doesn't conditionally omit the header.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt`:
- Around line 173-181: The new comment in BackendApiClient.kt conflicts with the
existing `**/data/**/*.kt` guideline and the PR description: either update the
guideline and docs to match the current behavior (forward X_GITHUB_TOKEN_HEADER
to passthrough routes) or change the code to restrict forwarding to only
`/v1/search` and `/v1/search/explore`. To fix, choose one of the two options and
apply consistent changes: A) If keeping forwarding, update the comment in
BackendApiClient.kt, the PR description/commit message, and the
`**/data/**/*.kt` guideline to mention forwarding for repo/releases/readme/user
with the quota rationale, ensuring symbols referenced (getRepo, getReleases,
getReadme, getUser, currentUserGithubToken(), X_GITHUB_TOKEN_HEADER) remain
unchanged; or B) If restricting forwarding, remove or stop injecting
currentUserGithubToken()/X_GITHUB_TOKEN_HEADER from the methods getRepo,
getReleases, getReadme, and getUser (and any helper that adds the header),
update BackendApiClient.kt comment and PR description accordingly, and
run/update tests that expect header forwarding.
🪄 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: 918f43f4-8d4f-4ea1-bc1e-2ec94be3570e

📥 Commits

Reviewing files that changed from the base of the PR and between 28fcb98 and 4206f80.

📒 Files selected for processing (2)
  • CLAUDE.md
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt

Comment thread CLAUDE.md Outdated
Comment on lines +173 to +181
// X-GitHub-Token is forwarded on every backend route that does a live
// GitHub passthrough (search, search/explore, repo lazy-fetch, releases,
// readme, user). Sending the header lets the backend make the upstream
// call under the user's own 5000/hr OAuth quota instead of the
// anonymous 60/hr-per-IP shared bucket — without it a popular repo's
// releases list can poison the backend's negative cache for 15 min on
// a single quota burst. DB-only routes (categories, topics, events,
// auth/device/*, badge) ignore the header and don't get it.

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

AI summary overstates scope of changes, and this comment entrenches a guideline conflict.

Two issues here:

1. Inconsistent summary — the AI summary claims token retrieval and X_GITHUB_TOKEN_HEADER injection were removed from getRepo, getReleases, getReadme, and getUser. The actual code shows those four methods are unchanged (no ~ markers on lines 182–255); all four still call currentUserGithubToken() and forward the header. The only change in this file is this comment block.

2. Guideline conflict made explicit — the new comment formally endorses forwarding the token to all passthrough routes, which directly conflicts with the **/data/**/*.kt coding guideline:

"Only send X-GitHub-Token header on /v1/search and /v1/search/explore endpoints; never on other endpoints or in logs"

This file also matches **/network/**/*.kt, whose guideline says the opposite (forward to /v1/repo, /v1/releases, /v1/readme, /v1/user). Before this change the conflict was implicit; the comment now documents the contradicting policy intentionally, yet the **/data/**/*.kt rule has not been updated. One of these two guidelines must be revised to reflect the agreed-upon policy. The PR description ("Stop leaking the user's PAT to non-search endpoints") also contradicts the commit message ("keep forwarding to passthrough routes") and the actual code, compounding the confusion.

Please reconcile the two guidelines — either update **/data/**/*.kt to align with the passthrough-route policy (adding the quota-management rationale), or restrict the code to search/search-explore only and remove the forwarding from getRepo, getReleases, getReadme, and getUser. As per coding guidelines: "**/data/**/*.kt: Only send X-GitHub-Token header on /v1/search and /v1/search/explore endpoints; never on other endpoints or in logs" and "**/network/**/*.kt: Forward X-GitHub-Token header to GitHub API passthrough routes …".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt`
around lines 173 - 181, The new comment in BackendApiClient.kt conflicts with
the existing `**/data/**/*.kt` guideline and the PR description: either update
the guideline and docs to match the current behavior (forward
X_GITHUB_TOKEN_HEADER to passthrough routes) or change the code to restrict
forwarding to only `/v1/search` and `/v1/search/explore`. To fix, choose one of
the two options and apply consistent changes: A) If keeping forwarding, update
the comment in BackendApiClient.kt, the PR description/commit message, and the
`**/data/**/*.kt` guideline to mention forwarding for repo/releases/readme/user
with the quota rationale, ensuring symbols referenced (getRepo, getReleases,
getReadme, getUser, currentUserGithubToken(), X_GITHUB_TOKEN_HEADER) remain
unchanged; or B) If restricting forwarding, remove or stop injecting
currentUserGithubToken()/X_GITHUB_TOKEN_HEADER from the methods getRepo,
getReleases, getReadme, and getUser (and any helper that adds the header),
update BackendApiClient.kt comment and PR description accordingly, and
run/update tests that expect header forwarding.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
AGENTS.md (1)

94-94: AGENTS.md documentation is accurate; the suggested improvement is optional.

The X-GitHub-Token forwarding policy in AGENTS.md Line 94 is correct and matches the implementation in BackendApiClient.kt — tokens are forwarded to /v1/search, /v1/search/explore, /v1/repo, /v1/releases, /v1/readme, and /v1/user. /v1/auth/exchange does not exist in the codebase, so there is no current security risk.

If you wish to add explicit defensive language about never forwarding the token on auth routes (to prevent future regression), the suggested diff is reasonable. However, this is a documentation enhancement rather than a correction of an existing error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 94, AGENTS.md correctly documents X-GitHub-Token
forwarding, but to avoid future regressions add a brief defensive sentence
clarifying that auth routes never receive the token and reference the
implementation points: mention BackendApiClient.currentUserGithubToken()
(private, never logged) and AuthenticationStateImpl (debounces 401s) and
explicitly list the passthrough routes (/v1/search, /v1/search/explore,
/v1/repo/{owner}/{name}, /v1/releases/{owner}/{name}, /v1/readme/{owner}/{name},
/v1/user/{username}) and DB-only routes so readers know which endpoints must not
receive the token; keep the change to documentation only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@AGENTS.md`:
- Line 94: AGENTS.md correctly documents X-GitHub-Token forwarding, but to avoid
future regressions add a brief defensive sentence clarifying that auth routes
never receive the token and reference the implementation points: mention
BackendApiClient.currentUserGithubToken() (private, never logged) and
AuthenticationStateImpl (debounces 401s) and explicitly list the passthrough
routes (/v1/search, /v1/search/explore, /v1/repo/{owner}/{name},
/v1/releases/{owner}/{name}, /v1/readme/{owner}/{name}, /v1/user/{username}) and
DB-only routes so readers know which endpoints must not receive the token; keep
the change to documentation only.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 239b8c18-4d79-45e8-97e6-8a1864964a0a

📥 Commits

Reviewing files that changed from the base of the PR and between 4206f80 and 2b209c3.

📒 Files selected for processing (1)
  • AGENTS.md

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt (1)

46-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Break the streak on every non-401 response.

200..299 is too narrow for the debounce contract. A 401 -> 403/404/5xx -> 401 sequence is not consecutive auth failure, but this branch still leaves the first 401 counted and can clear a valid session on the next 401. Call the reset hook for any statusCode != 401.

Suggested change
                 when {
                     statusCode == 401 -> {
                         plugin.scope.launch {
                             plugin.authenticationState.notifySessionExpired(tokenKey)
                         }
                     }
-                    statusCode in 200..299 -> {
+                    else -> {
                         plugin.scope.launch {
                             plugin.authenticationState.notifyRequestSucceeded(tokenKey)
                         }
                     }
                 }

As per coding guidelines, "401 responses from passthrough routes do not indicate session expiration—AuthenticationStateImpl must debounce consecutive 401s under the same token before clearing the session."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt`
around lines 46 - 55, The current branch only treats 200..299 as a "non-401"
reset, which misses other non-401 responses and breaks the debounce logic;
inside UnauthorizedInterceptor (around the when handling statusCode) change the
logic so that statusCode == 401 triggers plugin.scope.launch {
plugin.authenticationState.notifySessionExpired(tokenKey) } and any other
statusCode (i.e., statusCode != 401) triggers plugin.scope.launch {
plugin.authenticationState.notifyRequestSucceeded(tokenKey) } (or the reset hook
used to clear the 401 streak), ensuring every non-401 response resets the
consecutive-401 debounce for the given tokenKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt`:
- Around line 47-55: The current UnauthorizedInterceptor uses
plugin.scope.launch to call
plugin.authenticationState.notifySessionExpired(tokenKey) and
notifyRequestSucceeded(tokenKey), which allows those updates to run
out-of-order; remove the fire-and-forget launches and invoke
plugin.authenticationState.notifySessionExpired(tokenKey) and
plugin.authenticationState.notifyRequestSucceeded(tokenKey) directly (inline) in
the suspend pipeline so the auth-state methods execute in response order instead
of being scheduled on plugin.scope.

---

Duplicate comments:
In
`@core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt`:
- Around line 46-55: The current branch only treats 200..299 as a "non-401"
reset, which misses other non-401 responses and breaks the debounce logic;
inside UnauthorizedInterceptor (around the when handling statusCode) change the
logic so that statusCode == 401 triggers plugin.scope.launch {
plugin.authenticationState.notifySessionExpired(tokenKey) } and any other
statusCode (i.e., statusCode != 401) triggers plugin.scope.launch {
plugin.authenticationState.notifyRequestSucceeded(tokenKey) } (or the reset hook
used to clear the 401 streak), ensuring every non-401 response resets the
consecutive-401 debounce for the given tokenKey.
🪄 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: 2bff8eeb-f272-4f32-8aa4-fbd65adaa94d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b209c3 and 4ed99b6.

📒 Files selected for processing (4)
  • CLAUDE.md
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/interceptor/UnauthorizedInterceptor.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/AuthenticationStateImpl.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/AuthenticationState.kt

@rainxchzed rainxchzed merged commit 71b618a into main May 3, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/auth-resilience-and-token-scope branch May 3, 2026 06:41
@rainxchzed rainxchzed restored the fix/auth-resilience-and-token-scope branch May 3, 2026 06:44
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