Problem
The CLI authenticates via OIDC at kontext start and stores the access token for the session lifetime. The backend bearerTransport sends this token on every request (heartbeats, event ingestion, session end).
OIDC access tokens are short-lived (typically 1 hour with Hydra). For coding sessions that run longer than the token TTL, all authenticated requests start failing with unauthenticated — heartbeats, event ingestion, and session teardown all silently stop working.
2026/04/07 08:43:26 sidecar: heartbeat: unauthenticated: unauthenticated
2026/04/07 08:43:56 sidecar: heartbeat: unauthenticated: unauthenticated
...repeats every 30s for the rest of the session
Desired behavior
The sidecar should transparently refresh the access token before or when it expires, without interrupting the session or requiring user interaction.
Proposed approach
1. Persist the refresh token
The OIDC auth flow already returns a refresh token alongside the access token. Store it in the session (on disk or keyring) so it's available to the sidecar during the session.
2. Proactive refresh in the sidecar
The sidecar knows the token's expires_at time. Before expiry (e.g., when 80% of the TTL has elapsed), proactively use the refresh_token grant to obtain a new access token. Update the bearerTransport atomically so in-flight requests aren't affected.
3. Reactive refresh on 401
As a fallback, if any RPC returns Unauthenticated, attempt a refresh before retrying. This handles clock skew and edge cases where proactive refresh didn't fire in time.
4. Refresh failure → re-auth prompt
If the refresh token itself is expired or revoked, the sidecar can't recover silently. In this case, surface a clear message to the user (e.g., "Session expired, run kontext login to re-authenticate") rather than silently failing every 30 seconds.
Implementation notes
- The
bearerTransport in internal/backend/backend.go currently holds a static token string. It needs to become token-aware: hold a reference to a refreshable token source instead.
- The OIDC client registration must include
refresh_token in its grant_types.
- The refresh token should be stored securely (system keyring or encrypted on disk), not as a plaintext env var.
- Consider making the
Client struct hold a TokenSource interface that the sidecar can swap transparently, similar to golang.org/x/oauth2.TokenSource.
Problem
The CLI authenticates via OIDC at
kontext startand stores the access token for the session lifetime. The backendbearerTransportsends this token on every request (heartbeats, event ingestion, session end).OIDC access tokens are short-lived (typically 1 hour with Hydra). For coding sessions that run longer than the token TTL, all authenticated requests start failing with
unauthenticated— heartbeats, event ingestion, and session teardown all silently stop working.Desired behavior
The sidecar should transparently refresh the access token before or when it expires, without interrupting the session or requiring user interaction.
Proposed approach
1. Persist the refresh token
The OIDC auth flow already returns a refresh token alongside the access token. Store it in the session (on disk or keyring) so it's available to the sidecar during the session.
2. Proactive refresh in the sidecar
The sidecar knows the token's
expires_attime. Before expiry (e.g., when 80% of the TTL has elapsed), proactively use therefresh_tokengrant to obtain a new access token. Update thebearerTransportatomically so in-flight requests aren't affected.3. Reactive refresh on 401
As a fallback, if any RPC returns
Unauthenticated, attempt a refresh before retrying. This handles clock skew and edge cases where proactive refresh didn't fire in time.4. Refresh failure → re-auth prompt
If the refresh token itself is expired or revoked, the sidecar can't recover silently. In this case, surface a clear message to the user (e.g., "Session expired, run
kontext loginto re-authenticate") rather than silently failing every 30 seconds.Implementation notes
bearerTransportininternal/backend/backend.gocurrently holds a static token string. It needs to become token-aware: hold a reference to a refreshable token source instead.refresh_tokenin itsgrant_types.Clientstruct hold aTokenSourceinterface that the sidecar can swap transparently, similar togolang.org/x/oauth2.TokenSource.