Skip to content

Daemon does not reliably pick up a new credential after kcap login (live hub pins handshake identity; profile/tenant change never heals) #511

Description

@alexeyzimarev

Sibling of #509 (which makes a hook 401 tell the user to run kcap login). Once they do, a running daemon does not reliably pick the new credential up.

What is already fine

Worth stating so a fix does not touch it: every daemon HTTP path resolves a token per operation through TokenStore, which has no in-process cache — ServerConnection.ProcessEventQueueAsync (:1247), EvalRunner (:50, :99, :144), SpoolDrainLoop (:52), AgentOrchestrator (:262, :2824). Those see a new token on the next call. TokenRefreshLoop likewise drives the shared, cross-process-locked TokenStore.RefreshIfExpiringAsync, so proactive refresh is not the problem either.

A hub connection that fails to authenticate also self-heals: ConnectWithRetryAsync retries forever (backoff capped at 30s) and each StartHubAsync re-invokes AccessTokenProvider (ServerConnection.cs:192), which re-reads the store.

Problem 1 — a live hub connection pins its handshake identity

SignalR invokes AccessTokenProvider only at negotiate. kcap-server sets no CloseOnAuthenticationExpiration on the hub, so the ClaimsPrincipal captured at handshake is pinned for the life of the socket. A daemon whose token expires — or is replaced by kcap login — keeps operating as the old identity until something independently drops the transport.

This usually presents as silent staleness rather than an error, which makes it easy to miss: work attributed to the previous identity, or a tenant switch that appears not to take effect. ForceReconnectAsync (:785) already exists and is driven by DaemonHeartbeatLoop, so there is a natural hook — nothing currently triggers it on a credential change.

Problem 2 — a profile or tenant change never heals, and never explains itself

TokenStore.GetValidTokensForServerAsync resolves the active profile, then withholds the token entirely unless it is bound to the requested server:

if (!BoundToTarget(snapshot, targetBaseUrl)) {
    return new(null, AuthStatus.WrongServer, snapshot.ServerUrl, profile);
}

The daemon always asks for its own configured _config.ServerUrl. So if kcap login lands on a different tenant (discovery lets you pick one), or the user runs kcap use to switch profiles, a daemon configured for the previous server gets WrongServer forever. No amount of retrying fixes a binding mismatch, but ConnectWithRetryAsync retries anyway, every 30s, in perpetuity — the daemon needs a restart or a reconfigure and nothing says so.

Diagnosability is the other half: the hub's AccessTokenProvider just returns null on WrongServer / Expired, discarding the reason. The daemon then logs a generic connection failure instead of "this profile's token was issued by " or "run kcap login" — the same unexplained-401 complaint as #509, one layer down.

Not yet reproduced

The originally-reported symptom ("after kcap login the daemon still used the old token") was an inference, not a confirmed observation, so the exact trigger is unverified. Both problems above are read off the code rather than from a repro. A third hypothesis worth testing while reproducing: a supervised daemon (launchd/systemd) runs with its own environment, so if HOME differs from the interactive shell's it would read a different token store entirely — that one is speculation, not verified.

Suggested first step: reproduce each path deliberately (expire a token under a live hub; kcap login into a second tenant with a daemon running; run the same under a supervised daemon) before choosing between "force a re-negotiate on credential change" and "detect the binding mismatch and report it instead of retrying forever". They may well need different fixes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions