Triage
Thanks for the write-up. The refresh request you quoted matches what T3 sends, and it also matches OpenAI's Refreshing tokens section: a form-encoded grant_type=refresh_token, the issued oaiapp_ client id (not dynamic_agent_client), the saved refresh token, and resource=https://api.openai.com/v1, with scope left out, posted to the discovered token endpoint.
Clearing the session on invalid_grant is the recovery OpenAI documents for an unusable refresh token (Refresh errors): drop the dead token set and run OAuth again with the saved client id. T3 does that and keeps the registration, so reconnecting doesn't create a new client. A transient failure such as 503 doesn't clear the credentials, and the refresh-recovery tests in CodexChatGptAuth.test.ts cover that path.
Two corrections to the T3-specific claims:
- This was reproduced with graff and curl, not with T3. The claim that every T3 connection dies after an hour is inferred from the request shape.
- "Your ChatGPT connection expired or was disconnected. Sign in again." isn't what this path shows.
exchangebuilds that error, butaccessthen remaps every refresh failure exceptinvalid_clientto "Could not renew the ChatGPT connection. Retry, or sign in again." The credentials are still removed before that remap.
ext_agent_host_id is required on the authorize request but isn't part of the documented refresh body, so leaving it off isn't a T3 bug. Attempts 5–7 also reused a token OpenAI had already refused, so they don't show whether a first refresh with extra fields would succeed.
There are no other reports of this from a T3 session. Managed ChatGPT auth landed in merged PR #14290 about ten hours before this issue was filed. If OpenAI is rejecting a spec-matching refresh from every client, the fix is on their side. T3 shouldn't keep retrying invalid_grant or keep a rejected refresh token around.
To treat this as a T3 bug, we need a capture from an actual T3 session, not another client. Sign in with ChatGPT in T3 and leave that session alone until after earliest_refresh_at and within a minute of the access token expiring. Then capture the token endpoint's status, error code, and x-request-id, with the refresh token and client id redacted. A graff x-request-id is useful to OpenAI, but it doesn't show that T3 hit the same refusal.