Silent OAuth2 re-authentication for long-running workflows
X tokens expire in two hours. Workflows ran for longer than that and failed with a 401 nobody could explain. The fix became the platform standard for every OAuth2 integration.
On a multi-agent workflow platform, several integrations used OAuth2: X, Strike, PayPal. Each issues short-lived access tokens, and X’s expire after two hours. When a token expired in the middle of an active workflow run, the API call failed with 401 Unauthorized and the workflow broke with no clear message to the user.
Why a generic fix did not work at first
Each provider has a slightly different token endpoint and refresh flow. X also has the extra edge case where the refresh token itself can be expired, so the mechanism has to fail loudly in that one case and quietly everywhere else.
The pattern
Wrap every authenticated call in a retry that understands exactly one thing: a 401 means “refresh, then try once more”.
async function withAuth<T>(conn: Connection, call: (token: string) => Promise<T>): Promise<T> {
try {
return await call(conn.accessToken);
} catch (err) {
if (!isUnauthorized(err)) throw err;
const fresh = await refresh(conn); // provider-specific, stored per connection
if (!fresh) throw new ReconnectRequired(conn.provider);
await connections.save({ ...conn, ...fresh });
return call(fresh.accessToken);
}
}
The provider-specific part is refresh. Everything else is shared. ReconnectRequired is the only error the user ever sees, and it says what to do.
Result
- Token expiry errors disappeared across all OAuth2 integrations.
- Workflows that used to fail silently mid-run now complete across long sessions.
- Users no longer reconnect accounts by hand.
- The wrapper became the standard for every new OAuth2 plugin on the platform.
Small pattern, big reliability win. The lesson I keep relearning: the boring retry wrapper is usually worth more than the clever feature next to it.