agent stored the refresh token but never wired refresh logic - the generated client worked for one hour then 401d on...
A playbook for adding missing token refresh: wrap API calls in a retry-on-401 layer that exchanges the stored refresh token for a new access token and retries once. Use when generated client code saved the refresh token but never implemented refresh, so every call 401s after the first hour. Not for wrong credentials, revoked refresh tokens, or providers that never issued one.
TL;DR
Storing the refresh token is not refresh logic. Wrap every API call in a layer that catches the first 401, exchanges the stored refresh token at the token endpoint, retries the request once with the new token, and only then surfaces the error. That one wrapper turns a client that dies hourly into one that runs indefinitely.
The query
agent stored the refresh token but never wired refresh logic - the generated client worked for one hour then 401d on every callSteps
1. Prove the failure is expiry, not credentials
Look at a failed request's response body and the token response from login. If the first hour worked and the error body says the access token is expired or invalid, the diagnosis is refresh, not credentials. Confirm a refresh token was actually stored alongside the access token.
Expected: evidence that the access token expired (timestamp comparison or provider error message) plus a stored refresh token value.
2. Confirm the provider supports refresh
Read the token endpoint docs or your own token response: a refresh_token grant type and an issued refresh token mean refresh is available. Some client-credentials integrations never get one; if none was issued, step 3 changes to re-authenticating from scratch.
Expected: a yes or no on refresh support, taken from the token response or docs, not assumed.
3. Add a retry-on-401 wrapper around all API calls
In the generated client's HTTP layer, catch a 401 response: call the token endpoint with the stored refresh token, save the new access token (and the new refresh token if one is returned - some providers rotate them), then retry the original request exactly once. If the retry also 401s, surface the error instead of looping.
Expected: one wrapper function every request path uses; after a 401 the client transparently gets a new token and the retried request returns 200.
4. Add proactive refresh before expiry
Do not wait for the 401. Read the token's expiry time when it is issued and refresh when, say, five minutes remain. This avoids the failed-request-then-retry cycle entirely on long jobs.
Expected: long-running jobs complete with zero 401-triggered retries in the logs.
5. Persist the refreshed tokens
Write the new access and refresh tokens back to wherever the client stores credentials (file, env, secret store). A process restart must not resurrect the hour-old expired token.
Expected: after a restart, the client loads the latest tokens and makes a working request without re-authenticating.
Use this when
- The client worked for about an hour (or one token lifetime) then 401d everywhere
- A refresh token exists in storage but nothing ever reads it
- The generated code has no token-endpoint call except the initial login
- 401 responses cluster at regular intervals matching the token lifetime
Not for this skill when
- Credentials are wrong from the first request (never worked, not expiry)
- The refresh token itself was revoked or rotated and refresh calls fail (different fix)
- The provider issues no refresh tokens for this grant type (re-authenticate instead)
- 401s arrive randomly with a valid fresh token (scope or account problem)
Variant phrasings
generated client worked for one hour then 401d on every call
That is the headline symptom. Steps 1 and 2 confirm it, steps 3 to 5 fix it permanently.
refresh token is stored in the database but never used
Same fix. The token sitting in storage is step 1's confirmation; step 3 is the wiring that was never generated.
Why it happens
The agent generated the happy path: authenticate once, store tokens, make calls. Refresh is invisible on the happy path because the first token works for the whole test session. Nothing in the docs' quickstart shows the 401-an-hour-later failure, so the agent never modeled it. Storing the refresh token felt like handling it, but storage without a refresh call is just a souvenir.
Edge cases
- Provider rotates refresh tokens: always save the newest refresh token from each refresh response, or the next refresh fails with an invalid-grant error.
- Concurrent requests all 401 at once: guard the refresh with a lock so one request refreshes and the others wait, instead of firing ten refresh calls that invalidate each other.
- Refresh token has its own expiry: long-lived jobs need a re-authentication path for when the refresh token itself dies. Log it loudly when it happens.
- Token endpoint rate-limits refresh calls: proactive refresh (step 4) keeps you far under the limit; retry storms from missing locks do not.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_QJ3rOx8WGt6IxTanyUPmCQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.