df972a85e2
Multiple codex workspaces share ONE ChatGPT-Pro OAuth token (global_secrets key CODEX_AUTH_JSON). OpenAI's refresh_token is single-use, so letting each per-agent codex app-server refresh on its own 401 burned the shared seed within seconds (a refresh storm → token_invalidated + "refresh token already used"). This adds a single platform-side owner of the refresh: - internal/codexauth/refresher.go: one background goroutine, structurally single-flight (one goroutine + package mutex). Reads the global CODEX_AUTH_JSON, decodes the access_token JWT exp, and only within a safety margin of expiry POSTs the refresh_token ONCE per due cycle, then re-encrypts and writes the rotated blob back to global_secrets. Inert when the secret is absent; on a permanent failure (invalid_grant / "already used") it logs once and does NOT hot-loop. Billing-mode resolution + byok are untouched. - cmd/server/main.go: wired under supervised.RunWithRecover like the other background sweeps. Pairs with the codex template's codex_auth_sync.sh (GET-only re-sync; per-agent OAuth POST disabled) so workspaces only consume the current token and never rotate it themselves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>