Skip to content

[OpenAI/Codex] Previously working K12 account now gets workspace 403 through OpenCodex and is marked needs-reauth #1789

Closed

Description

@jojodat

Client or integration

Codex App

Area

Authentication and account pool

Summary

Important control result: this K12 account has working Codex access. Prompts succeed in the official Codex product, and the same account previously also worked through OpenCodex. The current OpenCodex behavior is a regression.

This is therefore not a report that Codex is unavailable for K12, nor evidence that the workspace administrator disabled Codex. The exact first failing OpenCodex version is not known.

The account can be added and reauthenticated successfully in OpenCodex. OpenCodex stores a fresh credential, warm-up validation succeeds, and authenticated WHAM usage returns HTTP 200 with plan_type: "k12". Despite that, a prompt routed through OpenCodex immediately receives HTTP 403 with codex_workspace_access_denied.

OpenCodex then classifies that workspace-specific 403 as a credential failure and marks the account needs-reauth. Reauthentication succeeds, but the next OpenCodex-routed prompt recreates the same state.

Expected: OpenCodex should send the workspace context required for a working K12 Codex account. If upstream still returns a workspace-specific denial, OpenCodex should surface it as a workspace/request-context problem and keep the valid credential instead of presenting reauthentication as the remedy.

Reproduction

  1. Historical control: the same K12 account previously completed prompts successfully through OpenCodex.
  2. Current control: sign in with the K12 account directly in the official Codex product and send a prompt. It still succeeds.
  3. The official Codex Analytics page for the same account also shows active 5-hour and weekly usage limits.
  4. Add that same account through OpenCodex Codex Auth and complete OAuth.
  5. Confirm OpenCodex detects plan_type: "k12"; credential warm-up and WHAM usage both succeed.
  6. Select the K12 pool account and send a minimal prompt through OpenCodex using gpt-5.6-sol or gpt-5.6-luna.
  7. The OpenCodex-routed request returns HTTP 403 with codex_workspace_access_denied, and OpenCodex marks the account needs-reauth.
  8. Complete Re-authenticate and retry immediately. The credential validates successfully, but the same OpenCodex-only failure returns.

This reproduced for both tested models on every attempt. The successful direct Codex prompt distinguishes this from missing K12 entitlement; the earlier successful OpenCodex use additionally identifies the current behavior as a regression.

Version

2.20.0

Operating system

macOS 27.0 (arm64)

Provider and model

OpenAI / ChatGPT Codex workspace login (plan_type=k12); gpt-5.6-sol and gpt-5.6-luna

Logs or error output

Sanitized upstream response observed only on the OpenCodex-routed requests:

{
  "detail": {
    "message": "Unauthorized: Contact your ChatGPT workspace administrator for access.",
    "code": "codex_workspace_access_denied"
  }
}

Sanitized sequence:

direct prompt in official Codex product: succeeds
OpenCodex credential warm-up / validation: succeeds
OpenCodex WHAM usage: HTTP 200, authenticated, plan_type=k12
prompt via OpenCodex with gpt-5.6-sol: HTTP 403
prompt via OpenCodex with gpt-5.6-luna: HTTP 403
OpenCodex account status afterward: needs-reauth

Implementation clue in v2.20.0: classifyCodexUpstreamOutcome() in src/codex/routing.ts classifies every 401/403 as credential; that branch calls markAccountNeedsReauth(). The upstream code here explicitly describes workspace access, while the same credential is freshly validated and accepted by WHAM.

Screenshots and supporting files

Related K12 quota-window parser issue: #1791

Redacted configuration

{
  "codexAccounts": [
    {
      "plan": "k12",
      "isMain": false
    }
  ]
}

Emails, account/workspace IDs, tokens, request IDs, and local paths were intentionally omitted.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. coderabbitai commented on Aug 15, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #426 - feat(codex): add account-qualified model namespaces [open]
    #512 - feat(codex): add account namespace foundation [open]
    #671 - feat(codex): add exact account routing [merged]
    #1353 - fix(codex): retain quota cooldown after account retry [merged]
    #1488 - feat(codex): show per-account usage [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. jojodat commented on Aug 15, 2026

    @jojodat
    Author

    Related K12 quota parsing/dashboard issue: #1791. Both reports were reproduced with the same anonymized K12 pool account; #1791 includes the sanitized WHAM window structure needed to reproduce the parser defect without access to a K12 workspace.

  3. changed the title [-][OpenAI/Codex] K12 workspace account returns codex_workspace_access_denied and is falsely marked needs-reauth[/-] [+][OpenAI/Codex] Working K12 account fails only through OpenCodex and workspace 403 is mislabeled as needs-reauth[/+] on Aug 15, 2026
  4. changed the title [-][OpenAI/Codex] Working K12 account fails only through OpenCodex and workspace 403 is mislabeled as needs-reauth[/-] [+][OpenAI/Codex] Previously working K12 account now gets workspace 403 through OpenCodex and is marked needs-reauth[/+] on Aug 15, 2026
  5. jojodat commented on Aug 15, 2026

    @jojodat
    Author

    Native Codex control run (sanitized)

    I reproduced the control on a separate Linux x86_64 host on which OpenCodex is not installed.

    • Official standalone client: codex-cli 0.147.0
    • Login status: Logged in using ChatGPT
    • The authenticated WHAM response for that login confirms plan_type: "k12"
    • WHAM window lengths: 18000 seconds (5h) and 604800 seconds (7d)
    • Test flags included --ignore-user-config and --ephemeral, eliminating custom provider/base-URL configuration and persistent thread state
    • Model: gpt-5.6-sol
    • Prompt: Reply with exactly K12_NATIVE_OK and do not use tools.

    Sanitized native result (thread ID removed):

    {"type":"turn.started"}
    {"type":"item.completed","item":{"type":"agent_message","text":"K12_NATIVE_OK"}}
    {"type":"turn.completed","usage":{"input_tokens":12386,"cached_input_tokens":2816,"output_tokens":8,"reasoning_output_tokens":0}}

    This confirms all three points independently:

    1. The clean/native login is a K12 account.
    2. The K12 account currently has functional Codex model access.
    3. The 403 occurs on the OpenCodex-routed path, not on native Codex.

    No email, account/workspace ID, token, thread ID, hostname, IP address, username, or local path is included.

  6. lidge-jun commented on Aug 16, 2026

    @lidge-jun
    Owner

    Fixed on dev by #1848 (merged as 73440d4bb).

    The classifier mapped every 403 to credential, which called markAccountNeedsReauth. That is why your loop repeated: re-authentication succeeded because the credential was never the problem, and the next prompt reproduced the same workspace denial.

    403 now splits on structured evidence rather than status. The upstream body is read through the existing bounded, duplicate-key-safe reader, and an own-property code (top level or under error) matching an allowlist — including codex_workspace_access_denied — yields denial: "workspace". That travels on the existing outcome meta, so the Responses paths pick it up without every caller changing shape.

    A workspace outcome records the failure so routing can prefer a healthier account, but does not mark the account for reauthentication and does not sweep its thread affinity. Credential quarantine sweeps affinity because reauth is account-wide; a workspace denial is not, so existing bindings stay valid.

    It fails safe throughout: a 403 with no denial evidence, an unreadable body, a malformed body or an unknown code all keep the previous handling, so a genuinely revoked credential still prompts for reauthentication.

    What this does not do: it stops OpenCodex from giving you the wrong remedy, but it does not itself send workspace context that would make the K12 prompt succeed. If the 403 persists after this ships, that part is still open — please reopen with the observed response and I will scope it separately.

    Closing the misclassification scope as completed. The change is on dev and ships with the next release.

  7. added a commit that references this issue on Aug 16, 2026
    1fa0f6a
  8. jojodat commented on Aug 17, 2026

    @jojodat
    Author

    Please reopen this issue.

    Tested with OpenCodex 2.24.1. Reauthentication succeeded, the K12 credentials were valid, and WHAM returned HTTP 200 with plan_type: "k12".

    The published classification fix does not cover the actual denial shape:

    {"detail":{"code":"codex_workspace_access_denied","message":"..."}}

    It currently recognizes structured denial codes only at the top level or under error.code. In a local regression test, classification returned denial = null before the patch. After adding an own-property check for detail.code, it returned denial = "workspace", and a local hotfix removed the incorrect needs-reauth state.

    A fresh task explicitly bound to the K12 account and using an exact account selector completed with HTTP 200; routing logs unambiguously assigned it to the K12 pool account. By contrast, switching an existing Plus-bound task to the same exact K12 selector produced four reproducible HTTP 403 responses without fallback.

    This shows that K12 works through OpenCodex in a clean task. The remaining issue concerns cross-account/thread metadata on existing Plus-bound tasks, while the published classification fix still misses detail.code.

    Please:

    1. Support structured denial codes under own-property detail.code.
    2. Add a regression test for an existing Plus-bound thread switched to an exact K12 selector. It must either start clean/remove incompatible metadata or return a clear thread/account incompatibility error—never needs-reauth.

    No additional private data should be necessary. If a diagnostic build is needed, I can offer one anonymized K12 test run.

  9. added a commit that references this issue on Sep 17, 2026
    bfe034c
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

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions