fix: auto-refresh OIDC token with proactive + reactive strategy#17
Open
fix: auto-refresh OIDC token with proactive + reactive strategy#17
Conversation
- Proactive refresh: refresh when <15min remains on the token TTL (~75% of a 1h token), preventing latency spikes on heartbeat/event paths - Reactive refresh: retry once with forced refresh on 401 (handles clock skew, server-side revocation) - Fix body consumption bug: use req.GetBody to obtain a fresh body for the retry request, since req.Clone shares the original io.ReadCloser - Persist refreshed tokens to keyring so other processes see them Closes #9 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ffd4aee to
293a865
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #9 — OIDC access tokens expire during long coding sessions, causing heartbeats and event ingestion to silently fail.
What changed
Proactive refresh — The token source checks remaining TTL on every call. When less than 15 minutes remain (~75% of a typical 1h token), it refreshes ahead of time. This prevents latency spikes and avoids concurrent-refresh races on the heartbeat/event paths.
Reactive refresh (401 retry) —
bearerTransport.RoundTripretries once with a forced token refresh on 401. Handles clock skew, server-side revocation, and edge cases where proactive refresh didn't fire in time.Body consumption fix — The retry path now uses
req.GetBody()to obtain a fresh request body. Previously,req.Clone()shared the originalio.ReadCloser, so the retry would send an empty body (all ConnectRPC unary RPCs use POST).Token persistence — Refreshed tokens are saved to the system keyring so other processes and subsequent
kontext startruns see the new token.Changes
internal/backend/backend.go:TokenSourcetakesforceRefresh bool,RoundTripretries on 401 with fresh bodyinternal/run/run.go:newSessionTokenSourcehonorsforceRefresh,shouldProactiveRefreshtriggers at <15min remainingTest plan
kontext start --agent claudefor >1h, verify heartbeats continue after token expiry✓ Token refreshedappears in stderr when proactive refresh fires🤖 Generated with Claude Code