fix(mcp): refresh a Hub token YouTrack rejects instead of trusting expires_at #109
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/YT-50-refresh-hub-token-on-401"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The HTTP transport treated a stored Hub token's recorded
expires_atas proof of liveness. When Hub invalidated a token out of band (observed after a client-secret rotation), the row still had time left, so the proactive refresh inclient_for_usernever fired and the dead bearer was handed to every tool call. Each call then failed with an opaqueHTTP 401 ... Invalid tokenuntil the clock happened to passexpires_at, with no restart or re-auth path that recovered it.A 401 from YouTrack is now the authoritative signal that the token is dead.
call_with_reauthwraps every tool body at the singlerun_toolboundary all 38 tools funnel through: on a 401 it force-refreshes the Hub token via the newAppState::refreshed_client_for_user(ignoringexpires_at) and retries the call exactly once with the new bearer. A 401 that survives the refresh is a real authorization failure and propagates, as does the original 401 when the refresh itself fails (the grant-level recovery is tracked in YT-51). The proactive expiry check stays, so a known-expired token still costs no 401 round trip, and the two refreshes log distinguishable info lines carryinguser_id.Tool errors keep only the
Displayrendering ofClientError, soerror::is_unauthorizedrecognizes a 401 by that format and a unit test pins the matcher toClientError::Status's rendering rather than letting the two drift apart. Retrying means running a tool body twice, sorun_toolnow takesFnoverFnOnceand the tool argument structs deriveClone.#YT-50