Skip to content

fix: accept a provider id in auth login, and diagnose plan/account on a Codex 400 - #1181

Open
anandgupta42 wants to merge 2 commits into
mainfrom
fix/auth-login-provider-arg
Open

fix: accept a provider id in auth login, and diagnose plan/account on a Codex 400#1181
anandgupta42 wants to merge 2 commits into
mainfrom
fix/auth-login-provider-arg

Conversation

@anandgupta42

@anandgupta42 anandgupta42 commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #1180

Type of change

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

What does this PR do?

Fixes two auth-flow defects a user hit tonight.

Defect 1 — auth login <provider> misreads the provider name as a URL.

$ altimate-code auth login openai
┌  Add credential
Error: Failed to load auth provider metadata from openai: fetch() URL is invalid

The login positional was declared [url] (auth login [url] in --help) and its value went straight into fetch(\${url}/.well-known/opencode`). Typing the provider name is the natural thing to do — auth listprints provider ids andauth logout [provider]` accepts one — so the user gets a low-level fetch error that names no alternative.

The positional is now [target] and routes on shape rather than position: an http:/https: URL takes the well-known path exactly as before; anything else resolves as a provider id through the same code path --provider and the interactive picker already use, so auth login openai lands on OpenAI's login methods. The decision is one exported function, resolveLoginTarget(), used by both the handler and the command's instance predicate — those two previously each tested args.url separately, and a URL must skip instance bootstrap (it may load remote config with the stale token) while a provider id must not, so having one function makes it impossible for them to disagree. An unmatched id now says what to do next instead of only rejecting:

Error: Unknown provider "definitely-not-a-provider". Run `altimate-code auth login` with no arguments to pick from the list, or pass a full auth provider URL including https://.

Defect 2 — a wrong-account / insufficient-plan login is undiagnosable.

When the stored ChatGPT OAuth credential belongs to a free-plan account, every Codex model request fails with:

400 {"detail":"The 'gpt-5.6' model is not supported when using Codex with a ChatGPT account."}

The message blames the model; the actual problem is which account was signed in with, and nothing in the CLI said so. That cost an engineer an evening.

Two changes:

  • The Codex OAuth fetch wrapper matches that one 400 shape (on the stable phrase, not the whole sentence, so the quoted model id can change) and replaces the body with a message naming the plan and truncated account id the request actually used, plus the commands to switch accounts. Every other status and every other 400 passes through byte-for-byte.
  • auth list annotates OAuth credentials the same way, e.g. OpenAI oauth (pro plan, account 4f3a1b2c…), so a wrong account is visible before it costs a debugging session.

Both read packages/opencode/src/auth/oauth-claims.ts, which decodes the JWT payload of the stored access token without verifying it — these claims drive human-facing diagnostics only, never an authorization decision. Only chatgpt_plan_type and chatgpt_account_id are read (each of which appears either top-level or inside the https://api.openai.com/auth claim bag, depending on which flow minted the token). The module never returns, logs, or embeds token or refresh-token material, and the account id is truncated to eight characters everywhere it is shown. Because the token is not verified and the values land in a terminal, claims are sanitized at the extraction boundary — printable ASCII only, capped at 64 characters — so a hostile claim cannot inject escape sequences or wallpaper the output. parseJwtClaims in the Codex plugin now delegates to the same decoder so there is a single implementation.

The message is plan-aware. An account on a plan that does include Codex (plus/pro/team/business/enterprise) can hit the same 400 for a model that simply is not offered to ChatGPT-subscription accounts; telling that user their plan is too low would contradict what they are paying for, so that case says the model, not the plan, is the problem, and points at auth list in case the stored account is not the intended one.

Not changed: the model allowlist (OAUTH_ALLOWED_MODELS is untouched, and test/plugin/codex-allowlist.test.ts still passes unmodified), and the originator / User-Agent headers we send.

How did you verify your code works?

Automated (no network in any of it — globalThis.fetch is stubbed and the tokens are synthetic JWT-shaped strings with alg: none):

  • packages/opencode/test/auth/oauth-claims.test.ts (23 tests) — claim extraction from both locations, sanitization of control characters and over-long claims, masking, the auth list annotation, matching/not-matching 400 bodies, both branches of the enriched message (unentitled and entitled plans), and assertions that no rendered string contains any segment of the token or the full account id.
  • packages/opencode/test/plugin/codex-plan-mismatch.test.ts (6 tests) — drives the real plugin loader's fetch wrapper: a free-plan 400 is rewritten with plan + masked account + the switch-account commands; a token with no readable claims still produces actionable text; other 400s and 200s pass through unchanged; requests still rewrite to the Codex endpoint.
  • packages/opencode/test/cli/auth-login-target.test.ts (11 tests) — provider ids route to the provider path, real URLs (including trailing-slash stripping) still take the well-known path, no argument still opens the picker, --provider still works and beats the positional, non-http schemes are rejected.
  • Suites run: test/plugin + test/cli → 897 pass / 24 skip / 0 fail (includes the updated help snapshot and the unmodified codex-allowlist.test.ts); test/auth → all pass. One flake seen on an early test/plugin run — loader-shared.test.ts "loads a file:// plugin function export" timed out at 5s — which passes on a clean checkout of main and on every subsequent run here; unrelated to this change.

Manual, against the real CLI:

  • auth --help now shows auth login [target].
  • auth login openai --method "Manually enter API Key" reaches OpenAI's API-key prompt instead of the fetch error (aborted at the prompt; nothing written).
  • auth login https://auth.invalid-host-for-test.example still takes the well-known path (fails with the connection error, proving URL handling is intact).
  • auth login definitely-not-a-provider prints the actionable message above.
  • auth list on a real credential renders OpenAI oauth (pro plan, account …).

Gates: bun run typecheck clean (13/13 tasks); bun run script/upstream/analyze.ts --markers --base main --strict clean, with every edit to an upstream-shared file wrapped in altimate_change markers.

Not verified: the enriched error cannot be exercised end-to-end without a real free-plan ChatGPT credential, which I do not have. The rewrite path is covered by unit tests that feed the exact 400 body through the real fetch wrapper, and the claim names were confirmed against a real (Pro) credential via auth list, but nobody has watched a live free-plan account produce this message. The claim locations for a free-plan token are inferred from the same token layout, not observed.

Reviewed with a second model. Its findings that fall inside this change were fixed in the second commit (claim sanitization, the contradictory message for entitled plans, an ambiguous doc comment). Two findings it raised are pre-existing and out of scope, and are left as-is rather than folded into a bug-fix PR: refreshAccessToken includes up to 200 characters of the OAuth token-endpoint error body in its message, and the well-known login path accepts plain http:// URLs and prints the URL verbatim.

Screenshots / recordings

Terminal output is quoted inline above.

Checklist

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

…on a Codex 400

Two auth-flow defects, both hit by a user tonight.

1. `auth login <provider>` misread the provider name as a URL.

The login positional was declared `[url]` and passed straight to
`fetch(`${url}/.well-known/opencode`)`, so `auth login openai` — the
natural thing to type, given `auth list` prints provider ids and
`auth logout [provider]` takes one — died with
"Failed to load auth provider metadata from openai: fetch() URL is invalid".

The positional is now `[target]` and routes on its shape: an http(s) URL
keeps the well-known behavior unchanged, anything else resolves as a
provider id through the same path `--provider` and the interactive picker
use. Routing lives in `resolveLoginTarget()` so the `instance` predicate
and the handler cannot disagree about which path an invocation takes. An
unmatched id now names the alternatives instead of just rejecting.

2. A wrong-account / insufficient-plan login was undiagnosable.

A ChatGPT OAuth credential on a free-plan account fails every Codex model
request with `{"detail":"The '<model>' model is not supported when using
Codex with a ChatGPT account."}` — which blames the model and never says
the real problem is which account was signed in with.

The Codex fetch wrapper now recognises that one 400 shape and rewrites it
into a message naming the plan and the truncated account id the request
actually used, plus the commands to switch accounts. `auth list` gained the
same annotation for OAuth credentials, so a wrong account is visible before
it costs a debugging session.

The claims come from `auth/oauth-claims.ts`, which decodes only the
non-secret `chatgpt_plan_type` / `chatgpt_account_id` claims (unverified —
diagnostics only, never an authorization decision). Nothing there returns,
logs, or embeds token material, the account id is truncated to eight
characters, and tests assert the rendered strings contain no segment of the
token. `parseJwtClaims` in the Codex plugin now delegates to the same
decoder so there is one implementation.

Model allowlists are untouched; originator/User-Agent headers are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T18:26:02.021586Z 7d53631 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 36 minutes.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe1d2226-9ee4-4ae0-8d8e-193851a6affd

📥 Commits

Reviewing files that changed from the base of the PR and between 23e5903 and 7d53631.

⛔ Files ignored due to path filters (1)
  • packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (7)
  • packages/opencode/src/altimate/plugin/altimate.ts
  • packages/opencode/src/auth/oauth-claims.ts
  • packages/opencode/src/cli/cmd/providers.ts
  • packages/opencode/src/plugin/codex.ts
  • packages/opencode/test/auth/oauth-claims.test.ts
  • packages/opencode/test/cli/auth-login-target.test.ts
  • packages/opencode/test/plugin/codex-plan-mismatch.test.ts
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auth-login-provider-arg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdd742cd43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +106 to +109
const CODEX_PLAN_MISMATCH_PATTERN = /not supported when using Codex with a ChatGPT account/i

export function isCodexPlanMismatchBody(body: string | undefined): boolean {
return Boolean(body) && CODEX_PLAN_MISMATCH_PATTERN.test(body!)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check the decoded plan before diagnosing an entitlement failure

When a valid Plus/Pro credential receives this wording because the requested model itself is unavailable or retired, this phrase-only matcher still rewrites the response as an account-plan failure. The resulting message can literally say the user is on the pro plan, then claim that Codex requires Plus or Pro and that the account cannot run it, directing them to log out instead of changing models. Gate the entitlement diagnosis on a known non-entitled plan, and preserve the original model error for entitled or unknown plans.

Useful? React with 👍 / 👎.

Comment on lines +312 to +313
const identity = result.type === "oauth" ? describeOAuthIdentity(result.access) : undefined
yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity})` : ""}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to the stored OAuth account ID

When an OpenAI credential gets its account ID from the ID token or the supported organizations[0].id fallback, login saves that value in result.accountId, but this annotation examines only the access-token claims and therefore omits the account entirely. The same omission affects the new mismatch diagnostic even though the request itself uses the stored accountId in ChatGPT-Account-Id, so affected multi-account credentials can still fail without identifying the account actually selected. Pass the stored account ID as a fallback when building both diagnostics.

Useful? React with 👍 / 👎.

.catch(() => "")
const enriched = enrichCodexPlanMismatchBody(body, currentAuth.access)
if (enriched) {
const identity = extractOAuthIdentity(currentAuth.access)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Redundant extractOAuthIdentity call — the identity is computed twice for the same token

enrichCodexPlanMismatchBody already calls extractOAuthIdentity(accessToken) internally (via codexPlanMismatchMessage), so this second call re-decodes and re-parses the same JWT payload solely to log the plan/account. Consider having enrichCodexPlanMismatchBody return the identity alongside the message and reuse it here instead of recomputing.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

* Decide which login path an invocation takes. A URL positional keeps the
* well-known behavior it always had; anything else is a provider id, so
* `auth login openai` works like `auth login --provider openai` rather than
* being handed to fetch(). An explicit `--provider` flag takes precedence over

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Comment misdescribes precedence — a URL positional actually beats --provider

The JSDoc states "An explicit --provider flag takes precedence over the positional", but resolveLoginTarget checks isAuthProviderUrl(args.target) first, so a URL positional wins over --provider (as the a URL positional keeps the well-known path even alongside --provider test asserts). The actual precedence is: URL positional > --provider > non-URL positional. Update this comment (and the matching claim in the PR description) to match the implementation.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/plugin/codex.ts 553 Redundant extractOAuthIdentity call — identity recomputed just for logging
Files Reviewed (4 files)
  • packages/opencode/src/auth/oauth-claims.ts - 0 issues
  • packages/opencode/src/cli/cmd/providers.ts - 0 issues
  • packages/opencode/src/plugin/codex.ts - 1 issue
  • packages/opencode/test/auth/oauth-claims.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous Review Summary (commit bdd742c)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit bdd742c)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/plugin/codex.ts 553 Redundant extractOAuthIdentity call — identity recomputed just for logging
packages/opencode/src/cli/cmd/providers.ts 241 JSDoc misdescribes --provider precedence (a URL positional actually wins)
Files Reviewed (8 files)
  • packages/opencode/src/altimate/plugin/altimate.ts - 0 issues
  • packages/opencode/src/auth/oauth-claims.ts - 0 issues
  • packages/opencode/src/cli/cmd/providers.ts - 1 issue
  • packages/opencode/src/plugin/codex.ts - 1 issue
  • packages/opencode/test/auth/oauth-claims.test.ts - 0 issues
  • packages/opencode/test/cli/auth-login-target.test.ts - 0 issues
  • packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap - 0 issues
  • packages/opencode/test/plugin/codex-plan-mismatch.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4-pro · Input: 43.9K · Output: 28.4K · Cached: 415.5K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/cli/cmd/providers.ts">

<violation number="1" location="packages/opencode/src/cli/cmd/providers.ts:312">
P2: Fall back to the stored OAuth account ID when the access token has no account claim. Otherwise `auth list` and the Codex mismatch diagnostic can omit the account that the request actually selected.</violation>

<violation number="2" location="packages/opencode/src/cli/cmd/providers.ts:313">
P2: When an OAuth token contains control characters in `chatgpt_plan_type`, this interpolation sends them to `@clack/prompts`, enabling newline or ANSI terminal injection in `auth list`. Strip control characters or whitelist the claims before rendering them.</violation>
</file>

<file name="packages/opencode/src/auth/oauth-claims.ts">

<violation number="1" location="packages/opencode/src/auth/oauth-claims.ts:106">
P2: This phrase-only matcher rewrites model-specific 400s for Plus/Pro or unknown-plan credentials as entitlement failures. Gate the rewrite on a known non-entitled plan, and preserve the original model error for entitled or unknown plans.</violation>

<violation number="2" location="packages/opencode/src/auth/oauth-claims.ts:116">
P2: When a claim or matched provider detail contains control characters, this helper emits them unchanged into terminal-facing output. Sanitize or allowlist plan/account values and strip and bound `originalDetail` before rendering `auth list` and Codex errors.

(Based on your team's feedback about preventing response-body leaks in errors.)</violation>
</file>

<file name="packages/opencode/src/plugin/codex.ts">

<violation number="1" location="packages/opencode/src/plugin/codex.ts:553">
P3: On every matching Codex 400, this decodes and parses `currentAuth.access` a second time after `enrichCodexPlanMismatchBody` already decoded it. Return the identity alongside the enriched message and reuse it for logging.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

* Build the actionable replacement for that 400. Names the account and plan the
* request actually used, then gives the exact commands to switch accounts.
*/
export function codexPlanMismatchMessage(identity: OAuthIdentity, originalDetail?: string): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When a claim or matched provider detail contains control characters, this helper emits them unchanged into terminal-facing output. Sanitize or allowlist plan/account values and strip and bound originalDetail before rendering auth list and Codex errors.

(Based on your team's feedback about preventing response-body leaks in errors.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/auth/oauth-claims.ts, line 116:

<comment>When a claim or matched provider detail contains control characters, this helper emits them unchanged into terminal-facing output. Sanitize or allowlist plan/account values and strip and bound `originalDetail` before rendering `auth list` and Codex errors.

(Based on your team's feedback about preventing response-body leaks in errors.) </comment>

<file context>
@@ -0,0 +1,150 @@
+ * Build the actionable replacement for that 400. Names the account and plan the
+ * request actually used, then gives the exact commands to switch accounts.
+ */
+export function codexPlanMismatchMessage(identity: OAuthIdentity, originalDetail?: string): string {
+  const masked = maskAccountId(identity.accountId)
+  const who = masked ? `Signed in as ChatGPT account ${masked}` : "Signed in with a ChatGPT account"
</file context>

// unexplained model error. Only non-secret claims are decoded, and the account id
// is truncated; the token itself is never rendered.
const identity = result.type === "oauth" ? describeOAuthIdentity(result.access) : undefined
yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity})` : ""}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When an OAuth token contains control characters in chatgpt_plan_type, this interpolation sends them to @clack/prompts, enabling newline or ANSI terminal injection in auth list. Strip control characters or whitelist the claims before rendering them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/providers.ts, line 313:

<comment>When an OAuth token contains control characters in `chatgpt_plan_type`, this interpolation sends them to `@clack/prompts`, enabling newline or ANSI terminal injection in `auth list`. Strip control characters or whitelist the claims before rendering them.</comment>

<file context>
@@ -265,7 +305,13 @@ export const ProvidersListCommand = effectCmd({
+      // unexplained model error. Only non-secret claims are decoded, and the account id
+      // is truncated; the token itself is never rendered.
+      const identity = result.type === "oauth" ? describeOAuthIdentity(result.access) : undefined
+      yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity})` : ""}`)
+      // altimate_change end
     }
</file context>
Suggested change
yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity})` : ""}`)
yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity.replace(/[\u0000-\u001f\u007f-\u009f]/g, "")})` : ""}`)

// so a wrong-account login is visible here instead of surfacing later as an
// unexplained model error. Only non-secret claims are decoded, and the account id
// is truncated; the token itself is never rendered.
const identity = result.type === "oauth" ? describeOAuthIdentity(result.access) : undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Fall back to the stored OAuth account ID when the access token has no account claim. Otherwise auth list and the Codex mismatch diagnostic can omit the account that the request actually selected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/providers.ts, line 312:

<comment>Fall back to the stored OAuth account ID when the access token has no account claim. Otherwise `auth list` and the Codex mismatch diagnostic can omit the account that the request actually selected.</comment>

<file context>
@@ -265,7 +305,13 @@ export const ProvidersListCommand = effectCmd({
+      // so a wrong-account login is visible here instead of surfacing later as an
+      // unexplained model error. Only non-secret claims are decoded, and the account id
+      // is truncated; the token itself is never rendered.
+      const identity = result.type === "oauth" ? describeOAuthIdentity(result.access) : undefined
+      yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}${identity ? ` (${identity})` : ""}`)
+      // altimate_change end
</file context>

* Matching on the stable phrase rather than the whole sentence keeps this
* working as the quoted model id changes.
*/
const CODEX_PLAN_MISMATCH_PATTERN = /not supported when using Codex with a ChatGPT account/i

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This phrase-only matcher rewrites model-specific 400s for Plus/Pro or unknown-plan credentials as entitlement failures. Gate the rewrite on a known non-entitled plan, and preserve the original model error for entitled or unknown plans.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/auth/oauth-claims.ts, line 106:

<comment>This phrase-only matcher rewrites model-specific 400s for Plus/Pro or unknown-plan credentials as entitlement failures. Gate the rewrite on a known non-entitled plan, and preserve the original model error for entitled or unknown plans.</comment>

<file context>
@@ -0,0 +1,150 @@
+ * Matching on the stable phrase rather than the whole sentence keeps this
+ * working as the quoted model id changes.
+ */
+const CODEX_PLAN_MISMATCH_PATTERN = /not supported when using Codex with a ChatGPT account/i
+
+export function isCodexPlanMismatchBody(body: string | undefined): boolean {
</file context>

Comment thread packages/opencode/src/cli/cmd/providers.ts Outdated
.catch(() => "")
const enriched = enrichCodexPlanMismatchBody(body, currentAuth.access)
if (enriched) {
const identity = extractOAuthIdentity(currentAuth.access)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: On every matching Codex 400, this decodes and parses currentAuth.access a second time after enrichCodexPlanMismatchBody already decoded it. Return the identity alongside the enriched message and reuse it for logging.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/plugin/codex.ts, line 553:

<comment>On every matching Codex 400, this decodes and parses `currentAuth.access` a second time after `enrichCodexPlanMismatchBody` already decoded it. Return the identity alongside the enriched message and reuse it for logging.</comment>

<file context>
@@ -532,10 +532,39 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
+                .catch(() => "")
+              const enriched = enrichCodexPlanMismatchBody(body, currentAuth.access)
+              if (enriched) {
+                const identity = extractOAuthIdentity(currentAuth.access)
+                log.warn("codex request rejected for this account's plan", {
+                  plan: identity.plan,
</file context>

Three review findings on the claim-based diagnostics:

- Claims come from a token we deliberately do not verify and are rendered
  straight into a terminal, so sanitize them at the extraction boundary:
  printable ASCII only (no escape or control sequences) and capped at 64
  characters. A claim that sanitizes to nothing is dropped.

- An entitled account (plus/pro/team/business/enterprise) can hit the same
  400 for a model that simply is not offered to ChatGPT-subscription
  accounts. Telling that user their plan is too low contradicts what they
  are paying for, so the message now branches: unentitled plans get the
  switch-accounts instructions, entitled ones are told the model — not the
  plan — is the problem, with a pointer to `auth list` in case the stored
  account is not the intended one.

- Clarify in `resolveLoginTarget`'s doc comment that a URL positional always
  wins (it is the only route to the well-known path) and `--provider` beats
  the positional only when the positional is not a URL. Behavior unchanged;
  the previous wording read as if the flag always won.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqnuBDGkh1ZT65Ti7e6DHZ
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d53631b73

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* diagnostics only, never for an authorization decision. Returns undefined for
* anything that is not a three-segment token with a JSON object payload.
*/
export function decodeJwtClaims(token: string): Record<string, unknown> | undefined {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required OAuthClaims self-reexport

This new module exposes only direct named exports and omits the package-required self-reexport, so its new consumers import individual helpers instead of the namespace projection mandated for modules in this package. Add the OAuthClaims self-reexport and consume the helpers through it.

AGENTS.md reference: packages/opencode/AGENTS.md:L17-L20

Useful? React with 👍 / 👎.

Comment on lines +558 to +562
return new Response(JSON.stringify({ detail: enriched }), {
status: response.status,
statusText: response.statusText,
headers: { "content-type": "application/json" },
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve upstream headers on enriched errors

When the matched 400 includes diagnostic headers such as a request identifier, constructing the replacement with only content-type discards every upstream header before the AI SDK can propagate them into the resulting API error. This makes these failures harder to correlate with provider logs; preserve the original response headers while replacing the body, adjusting entity-specific headers such as content-length as needed.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/auth/oauth-claims.ts">

<violation number="1" location="packages/opencode/src/auth/oauth-claims.ts:143">
P3: The new entitled-plan branch of `codexPlanMismatchMessage` tells a user whose account may be the wrong one to check `altimate-code auth list`, but then omits the switch-account commands that the sibling unentitled branch in the same function provides (`altimate-code auth logout openai` / `altimate-code auth login openai`). If the diagnosis is "that account is not the one you meant to use", the user is left with a pointer to the list but no way to switch, which contradicts the PR's stated goal of showing switch-account commands. Add the same logout/login commands here so the guidance is actionable.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

`${who} on the \`${identity.plan}\` plan, which does include Codex — so this is the model, ` +
`not the plan. This model is not offered to ChatGPT-subscription accounts; pick a different ` +
`model, or use an OpenAI API key.\n` +
`If that account is not the one you meant to use, \`altimate-code auth list\` shows which is stored.` +

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new entitled-plan branch of codexPlanMismatchMessage tells a user whose account may be the wrong one to check altimate-code auth list, but then omits the switch-account commands that the sibling unentitled branch in the same function provides (altimate-code auth logout openai / altimate-code auth login openai). If the diagnosis is "that account is not the one you meant to use", the user is left with a pointer to the list but no way to switch, which contradicts the PR's stated goal of showing switch-account commands. Add the same logout/login commands here so the guidance is actionable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/auth/oauth-claims.ts, line 143:

<comment>The new entitled-plan branch of `codexPlanMismatchMessage` tells a user whose account may be the wrong one to check `altimate-code auth list`, but then omits the switch-account commands that the sibling unentitled branch in the same function provides (`altimate-code auth logout openai` / `altimate-code auth login openai`). If the diagnosis is "that account is not the one you meant to use", the user is left with a pointer to the list but no way to switch, which contradicts the PR's stated goal of showing switch-account commands. Add the same logout/login commands here so the guidance is actionable.</comment>

<file context>
@@ -109,13 +120,31 @@ export function isCodexPlanMismatchBody(body: string | undefined): boolean {
+      `${who} on the \`${identity.plan}\` plan, which does include Codex — so this is the model, ` +
+      `not the plan. This model is not offered to ChatGPT-subscription accounts; pick a different ` +
+      `model, or use an OpenAI API key.\n` +
+      `If that account is not the one you meant to use, \`altimate-code auth list\` shows which is stored.` +
+      provider
+    )
</file context>

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auth: login <provider> fails as an invalid URL, and a free-plan ChatGPT login gives no diagnosis

1 participant