Skip to content

fix: retry transient OIDC refresh failures instead of disabling the controller - #129

Merged
peterj merged 2 commits into
agentdesktop-dev:mainfrom
devmittal02:fix/oidc-refresh-transient-retry
Sep 29, 2026
Merged

peterj merged 2 commits into
agentdesktop-dev:mainfrom
devmittal02:fix/oidc-refresh-transient-retry

Conversation

@devmittal02

Copy link
Copy Markdown
Contributor

Fixes #113

Problem

When the daemon refreshed its OIDC access token and hit a transient error (connection reset, DNS failure, timeout, 5xx from the token endpoint), remote::run returned Err. The spawn site in daemon.rs then set enrollment to "failed", logged controller integration disabled and exited the task, so nothing ever reconnected. The only recovery was restarting the daemon process. "Try again" in the desktop UI couldn't help, because the task it depends on was already gone.

Only 400/401/403 responses (for example invalid_grant) mean the refresh token is actually unusable, and those were already handled by re-enrolling.

Change

  • Transient refresh failures are retried. A refresh error that is_oauth_refresh_rejected doesn't classify as a rejection now:

    • logs a warning;
    • keeps the stored identity and refresh token;
    • waits with the existing exponential backoff (1s doubling to 60s), still honouring logout requests while waiting;
    • retries.

    The enrollment state stays enrolled throughout.

  • The controller session is supervised. remote::run now wraps the session loop (run_session), so it no longer returns. Any other error, such as a local identity store failure, is logged, surfaced as failed, and followed by a backoff restart instead of disabling the integration permanently.

    • The backoff resets if the failed session had been up longer than the maximum delay.
    • Logout requests that arrive between sessions are still completed.
  • daemon.rs just spawns remote::run.

Tests

  • New network_failures_during_refresh_are_transient test. It checks that real reqwest connect-refused and timeout errors, wrapped in the same context oidc::refresh adds, are not treated as token rejections.
  • The existing distinguishes_rejected_refresh_tokens_from_transient_errors test still covers 400/401 (rejected) vs 500 (transient).
  • Local checks: cargo fmt --all, make lint and cargo test --workspace --lib --bins all pass, and make generate-schema produces no diff.

🤖 Generated with Claude Code

…ontroller

A network error, timeout, or token endpoint outage while refreshing the
OIDC access token returned out of remote::run, and the daemon then marked
enrollment "failed" and never restarted the controller task. The device
stayed disconnected until the daemon process was restarted; "Try again"
in the desktop UI could not recover it.

- Treat refresh failures that are not an explicit 400/401/403 rejection as
  transient: keep the stored identity and refresh token, back off, and
  retry. Logout requests are still honoured during the backoff.
- Supervise the controller session inside remote::run so any remaining
  error (for example a local identity store failure) restarts the session
  with backoff instead of permanently disabling the integration.

Fixes agentdesktop-dev#113

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@devmittal02

Copy link
Copy Markdown
Contributor Author

@howardjohn can you review this

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The refresh retry branch also swallows local identity-store failures that should reach the supervisor.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Adds resilient controller-session supervision and retry behavior for transient OIDC refresh failures.

Changes:

  • Retries transient token refresh failures with exponential backoff.
  • Supervises failed controller sessions and handles logout between retries.
  • Simplifies daemon task spawning.
File Description
crates/​agent/​src/​remote.rs Adds refresh retries, session supervision, and tests.
crates/​agent/​src/​daemon.rs Delegates supervision directly to remote::run.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/agent/src/remote.rs Outdated
Comment on lines +185 to +201
Err(error) => {
// Network failures and token endpoint outages are transient: the
// stored refresh token is still valid, so keep the identity and retry
// instead of tearing down the controller integration.
warn!(
controller = %controller.address,
retry_in_seconds = delay.as_secs(),
error = %format!("{error:#}"),
"OIDC access token refresh failed; retaining current identity and retrying"
);
if wait_before_retry(delay, logout, &identity_path, &identity, enrollment).await
{
break;
}
delay = next_retry_delay(delay);
continue;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e23c123. The in-place retry arm now only matches errors whose chain contains a reqwest::Error (is_transient_refresh_failure). Anything else, such as identity::load/identity::save failures or an unsupported token type, is returned from run_session, so the supervisor sets enrollment to failed and applies its backoff. I added tests covering both cases (connect/timeout/503 are transient; an IO error from persisting the identity is not).

@peterj peterj left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks for the PR! Can you address the comments?

The in-place retry arm caught every error from refresh_oauth_if_needed,
including identity::load/save failures, so a broken local identity store
left enrollment reported as "enrolled" and looped forever. Retry only
when the error chain contains a reqwest::Error and propagate everything
else to the supervisor, which reports "failed".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@devmittal02
devmittal02 requested a review from peterj September 28, 2026 20:28
@devmittal02

Copy link
Copy Markdown
Contributor Author

Done, fixed the comments

@peterj
peterj merged commit d867b67 into agentdesktop-dev:main Sep 29, 2026
3 checks passed
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.

Daemon disables controller integration on transient OIDC token refresh failure instead of retrying

3 participants