Skip to content

fix(core): isolate ChatGPT account registrations - #56302

Open
KomeijiReimu wants to merge 3 commits into
anomalyco:devfrom
KomeijiReimu:chatgpt-account-registration
Open

KomeijiReimu wants to merge 3 commits into
anomalyco:devfrom
KomeijiReimu:chatgpt-account-registration

Conversation

@KomeijiReimu

@KomeijiReimu KomeijiReimu commented Oct 17, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #56298

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

“Add account” reuses the active ChatGPT registration, so another account can be denied by OpenAI. New connections now use dynamic_agent_client; reauthorization preserves the selected account's client ID through an optional credentialID. Selecting a different OAuth method starts an independent connection.

The target is passed through the protocol, generated clients and plugin API. Existing callback checks and credential-persistence behavior are preserved. Synced with dev at fa1260d6ca; clients are regenerated with the current generator, and obsolete website OpenAPI files stay deleted.

How did you verify your code works?

  • Full core, TUI, server, client, plugin and protocol suites: 8,874 passed, 36 skipped, no failures (Bun 1.4.4, Node 24.19.0, isolated HOME/TMPDIR).
  • Core OAuth regressions: 540 passes across 10 repetitions; TUI integration dialog: 160 passes across 10 repetitions.
  • bun run check and bun run typecheck:tsc pass. Client generation is repeatable and the protocol OpenAPI freshness check passes.
  • Live two-account OpenAI browser authorization remains untested.

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" role="img" aria-labelledby="client-title client-desc">
  <title id="client-title">New and saved client paths</title>
  <desc id="client-desc">Logic diagram, not an application screenshot. New account creates a new client. Reauthorize reuses the saved client.</desc>
  <style>
    .client-flow{animation:client-flow 3s linear infinite}
    @keyframes client-flow{to{stroke-dashoffset:-48}}
    @media (prefers-reduced-motion:reduce){.client-flow{animation:none}}
  </style>
  <g fill="currentColor" font-family="sans-serif" font-size="11.5">
    <text x="8" y="25">New account</text>
    <text x="154" y="25">New client</text>
    <text x="8" y="57">Reauthorize</text>
    <text x="154" y="57">Saved client</text>
  </g>
  <g fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
    <path opacity=".22" d="M92 21h50M92 53h50"/>
    <path class="client-flow" opacity=".75" stroke-dasharray="2 46" d="M92 21h48"/>
    <path class="client-flow" opacity=".5" stroke-dasharray="3 5" d="M92 53h48"/>
    <path opacity=".65" d="M138 18l4 3-4 3M138 50l4 3-4 3"/>
  </g>
</svg>

Screenshots / recordings

Authentication routing only; the TUI layout is unchanged.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Use independent registrations for new accounts and pass an explicit credential target when reauthorizing an existing connection.

Closes anomalyco#56298
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fix looks right, but one TUI path now fails where it used to work. I couldn't confirm the fix end to end: OpenAI only returns the workspace denial on its live sign-in page, and testing that needs two real ChatGPT accounts. I read the code and ran the closest tests: core provider-chatgpt and integration (50 passed), the TUI integration dialog (15 passed) and the server fetch suite (13 passed). typecheck passes in core, plugin, tui, server, protocol and client.

A new connection now sends dynamic_agent_client, and a reauthorization uses that credential's own saved client ID. That matches the cause found in #56298. The new "no registered client ID" error shouldn't trap older accounts, because every ChatGPT credential is saved with a clientID.

Public API: the changes are additive and backwards compatible.

  • POST /api/integration/{integrationID}/connect/oauth takes a new optional credentialID in its body. The OpenAPI files and generated clients are updated to match.
  • Plugin API (effect and promise): IntegrationOAuthMethodRegistration.authorize(answer) becomes authorize(answer, context?: { credential?: Credential.OAuth }). Existing plugins that take one argument keep working.
  • The plugin host's integration.oauth.connect takes a new optional credentialID.

Concerns:

  • Picking a different method now fails. In "Sign in required", the TUI sends the account's ID with whichever OAuth method the user picks. OpenAI has three OAuth methods: "Sign in with ChatGPT" and the two Codex legacy ones. Picking a method other than the one the account was made with now fails with "OAuth credential belongs to another method". Before this PR, that path signed in and created a new credential. Details inline.
  • Tests are large. About 240 test lines go into provider-chatgpt.test.ts, mostly generic plugin-adapter tests (details inline).

location,
dialog,
onConnected,
connection.method === "oauth" ? connection.id : undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This credentialID goes to selectMethod and is used with whichever OAuth method the user picks from the full list. OpenAI has three OAuth methods (Sign in with ChatGPT, Codex browser (legacy), Codex device code (legacy)). Connections only say method: "oauth", so the TUI can't tell which method the account was made with. If someone picks a Codex method to sign in again to an expired ChatGPT account (or the reverse), core now rejects it with "OAuth credential belongs to another method". Before this PR, that path worked and created a new credential.

Two options: show the credential's method ID on the connection and only send credentialID (or only show that method) when the methods match. Or have core and the plugin ignore the target when the method differs, instead of failing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9b73e65. After validating that the target exists, belongs to the integration and is OAuth, core treats a different OAuth method as a new connection without reusing its credential context. Same-method reauthorization still uses the selected registration. Added core completion, legacy-to-token-sharing URL and TUI method-selection regressions; the two cross-method tests fail before the fix and pass afterward.

}
})

describe("OAuth plugin authorization context", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block tests generic plugin-host and promise-adapter plumbing (passing context, single-argument compatibility) inside the ChatGPT provider tests, and it's large for what it checks. Please cut it down to the core cases: the ChatGPT authorize URL uses dynamic_agent_client when there's no target and the target's client ID when there is one. The credentialID checks can go in one small case in integration.test.ts.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the 130-line generic adapter block and its import in 9b73e65. Provider authorization coverage is now ChatGPT-specific; target validation and the same/cross-method context contract live in compact integration-service tests. The existing single-argument OAuth case remains.

Start independent authorization when a saved credential uses another OAuth method, while retaining validation of invalid and foreign targets.
@KomeijiReimu

Copy link
Copy Markdown
Author

@opencode-agent Both review comments are addressed in 9b73e65. Please re-review the latest commit. Switching OAuth methods now starts an independent connection, and the generic adapter test block has been removed. Local bun run check passes; core tests pass 540 times across 10 repetitions, and all 16 TUI tests pass. Live two-account OpenAI browser authorization remains untested.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both concerns from my last review are fixed, and the new commit adds no problems I could find. I still couldn't confirm the actual fix, because OpenAI only returns the workspace denial on its live sign-in page and testing that needs two real ChatGPT accounts. On 9b73e65 I ran core provider-chatgpt and integration (49 passed) and the TUI integration dialog test (16 passed); typecheck passes in core and tui.

Fixed since last time:

  • Picking a different method: in "Sign in required", choosing a method other than the account's own now starts a fresh connection instead of failing with "belongs to another method". When that fresh connection is "Sign in with ChatGPT", it uses dynamic_agent_client, which the new ChatGPT test checks.
  • Large tests: the generic adapter block is gone from provider-chatgpt.test.ts. What's left is small and specific to the fix.

Public API: no change since my last review, all additions and backwards compatible. The connect/oauth endpoint, OpenAPI files and generated clients take a new optional credentialID. Plugin OAuth authorize(answer) becomes authorize(answer, context?: { credential?: Credential.OAuth }), and existing plugins that take one argument keep working.

No remaining concerns.

@opencode-agent

Copy link
Copy Markdown
Contributor

Thanks. I already re-reviewed 9b73e65 in my latest review, and both earlier comments are resolved there:

  • Switching methods: choosing a different OAuth method now starts a separate connection instead of failing.
  • Tests: the generic adapter test block is gone.

On that commit, the core provider-chatgpt and integration tests passed (49), the TUI dialog tests passed (16), and typecheck passes in core and tui. I have no remaining concerns. The one open item is the live sign-in with two real OpenAI accounts in different workspaces, which I can't do here. A maintainer with such accounts would need to confirm that part.

Regenerate clients with the current generator and keep the retired website OpenAPI artifacts removed.
@KomeijiReimu

Copy link
Copy Markdown
Author

Merged current dev (fa1260d) in 582746c and resolved the generated-client conflicts using the current generator. Kept the retired website OpenAPI files deleted. All six affected package suites pass (8,874 passed, 36 skipped), as do bun run check and bun run typecheck:tsc. OAuth regressions also pass 10 repetitions. The upstream code still reuses the active registration for Add account; live two-account OpenAI authorization remains untested. @opencode-agent Please re-review the updated merge.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openai: adding a second ChatGPT account fails with workspace scope denied

1 participant