Skip to content

[Bug]: ChatGPT connections end after an hour because every refresh returns invalid_grant #14356

Description

@justrach

This issue is AI-generated. An AI coding agent wrote it from our own reproduction against OpenAI's token endpoint. We have not run T3 Code itself; the T3 Code behavior below is read from its source.

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Connect a ChatGPT account with managed authentication (feat(codex): connect ChatGPT accounts with managed authentication #14290).
  2. Keep the connection past the access token's one-hour lifetime. CodexChatGptAuth renews it within 60 seconds of expiry, once earliest_refresh_at has passed (CodexChatGptAuth.ts L729-L748).
  3. OpenAI refuses the renewal.

Without T3 Code: take a token set from any client of this flow and, inside the renewal window, send the same request:

curl -sS https://auth.openai.com/api/accounts/oauth/token \
  --data-urlencode grant_type=refresh_token \
  --data-urlencode client_id="$ISSUED_CLIENT_ID" \
  --data-urlencode refresh_token="$REFRESH_TOKEN" \
  --data-urlencode resource=https://api.openai.com/v1

Expected behavior

HTTP 200 with a new access token and a replacement refresh token, so the connection stays up.

Actual behavior

OpenAI answers 400 {"error": "invalid_grant"} with no error_description. T3 Code treats invalid_grant on a refresh as a dead token (L391-L406): it removes the connection and shows "Your ChatGPT connection expired or was disconnected. Sign in again." So every connection should end about an hour after sign-in.

We sent this request eight times across three sign-ins: before and after earliest_refresh_at, 60 seconds before expiry (T3 Code's timing), after expiry, and with ext_agent_host_id or scope added or resource dropped (those three reused a token that had just been refused). Every one was refused, including never-used tokens inside the renewal window. pi and nolune send the same four fields.

Impact

Major degradation or frequent failure

Version or commit

main @ 0fcd5f9

Environment

Not environment-specific: the refusal comes from OpenAI's token endpoint. Reproduced with curl and Python on macOS.

Logs or stack traces

HTTP/1.1 400 Bad Request

{"error": "invalid_grant"}

Workaround

Sign in again when the connection ends. T3 Code's request matches the documented one, so there may be nothing to change here unless OpenAI says a refresh needs more.

Full report, with every attempt, a reproduction script, and the refresh code of T3 Code, pi and nolune: https://github.com/justrach/codegraff/blob/feat/chat-gpt-new/docs/upstream/openai-sign-in-refresh-invalid-grant.md

Tracked for graff in justrach/codegraff#1422.

Activity

  1. juliusmarminge commented on Sep 30, 2026

    @juliusmarminge
    Member

    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. exchange builds that error, but access then remaps every refresh failure except invalid_client to "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.

  2. justrach commented on Oct 4, 2026

    @justrach
    Author

    Disclosure: written by an AI coding assistant for the reporter, and reviewed before posting.

    Update: this reproduces with OpenAI's own reference implementation, so it isn't specific to T3 Code or to how any client builds the request.

    Using openai/sign-in-with-chatgpt-devkit packages/local, unmodified:

    • A new registration, without ext_agent_host_id (the devkit's default).
    • The devkit's own refresh, sent in its own window: past earliest_refresh_at, 39 s before expiry, on a never-used refresh token.
    • The result was 400 invalid_grant ("ChatGPT did not accept this authorization grant").

    A second client renewed its own fresh sign-in a few minutes later, inside its window, and got the same answer: "The refresh token has been invalidated. A fresh sign-in is required."

    That matches the triage above: the request is the documented one, so the fix has to come from OpenAI. Reported upstream with the full reproduction, code links for the devkit, T3 Code, pi and Codegraff, and what was ruled out: openai/sign-in-with-chatgpt-devkit#5

  3. stantaneous commented on Oct 4, 2026

    @stantaneous

    I'm also experiencing a similar one-hour expiry symptom in T3 Code itself on macOS, using managed ChatGPT sign-in with T3 Code Nightly 0.0.46-nightly.20261004.2644.

    The ChatGPT connection stops working after about one hour. I'm currently seeing a 429 error. Restarting T3 Code temporarily fixes it and restores access, so I have to restart T3 Code roughly every hour to keep working.

    For clarity, earlier diagnostic checks also confirmed 401 token_expired errors from the running managed Codex process. The current 429 is my reported symptom, not a separately verified diagnostic result. Since restarting restores access, I haven't confirmed that this is the same invalid_grant failure described in the original report.

  4. vignesh-oai commented on Oct 4, 2026

    @vignesh-oai

    @stantaneous This happens if your account had a change in security settings, this includes if you recently did a password reset, or enrolled in AAS / Daybreak Blue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs more infoInitial triage showed no bug. Awaiting more infoupstreamvia-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions