Renew a ChatGPT subscription login that ChatGPT rejects during a run
Problem
A customer's ChatGPT-subscription automation failed every run for about 20 hours with:
Codex automation harness failed: workspace routing discovery unauthorized (401)
Before a turn, Codex calls chatgpt.com/backend-api/accounts/check. ChatGPT returned 401 for the stored access token, even though the token had not reached its exp. Codex would normally refresh after a 401, but a run sandbox holds only the run-only refresh token. The worker renews the stored login only when the token is close to expiry (subscriptionAuthCoveringRun), so nothing renewed it. Runs recovered only after someone opened the model picker, which runs Codex with the real refresh token and saved a new login.
Change
runCodexAutomationthrowsSubscriptionLoginRejectedError(a subclass ofAutomationHarnessError) when a subscription run ends with a ChatGPT 401 (unauthorized (401)or401 Unauthorizedin the final Codex error).- On that error,
processAutomationRuncalls the newrefreshSubscriptiondependency (refreshSubscriptionInSandbox).account/readwithrefreshToken: truerefreshes even when the token has not expired, so the next run gets a new token. - The failing run still fails with the same message. Refresh failures are reported to Sentry, except
SubscriptionCredentialUnavailableError, which means another operation is already renewing the login.
Re-running the failed turn is out of scope. It would need a new Daytona secret and sandbox and would affect notification handling.
Verification
- New tests: the harness classifies the 401; the run renews the login on that error and does not renew it on other harness failures.
pnpm typecheck, eslint on the changed files, andvitest run apps/worker(673 passed) pass.
Summary by cubic
Renews a stored ChatGPT subscription login as soon as a run reports a ChatGPT 401, instead of waiting until the token nears expiry.
- A subscription run that ends with
unauthorized (401)or401 Unauthorizednow throws aSubscriptionLoginRejectedError, and the worker immediately refreshes the stored login so the next run uses a fresh token. - The failing run still fails with the same message. Refresh failures are reported to Sentry, except
SubscriptionCredentialUnavailableError, which means another run is already renewing the login.