Repository navigation
[OpenAI/Codex] Previously working K12 account now gets workspace 403 through OpenCodex and is marked needs-reauth #1789
Description
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
- Historical control: the same K12 account previously completed prompts successfully through OpenCodex.
- Current control: sign in with the K12 account directly in the official Codex product and send a prompt. It still succeeds.
- The official Codex Analytics page for the same account also shows active 5-hour and weekly usage limits.
- Add that same account through OpenCodex Codex Auth and complete OAuth.
- Confirm OpenCodex detects
plan_type: "k12"; credential warm-up and WHAM usage both succeed. - Select the K12 pool account and send a minimal prompt through OpenCodex using
gpt-5.6-solorgpt-5.6-luna. - The OpenCodex-routed request returns HTTP 403 with
codex_workspace_access_denied, and OpenCodex marks the accountneeds-reauth. - 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
coderabbitai commentedon Aug 15, 2026
🔗 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 plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
jojodat commentedon Aug 15, 2026
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.
[-][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[/+][-][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[/+]jojodat commentedon Aug 15, 2026
Native Codex control run (sanitized)
I reproduced the control on a separate Linux x86_64 host on which OpenCodex is not installed.
codex-cli 0.147.0Logged in using ChatGPTplan_type: "k12"18000seconds (5h) and604800seconds (7d)--ignore-user-configand--ephemeral, eliminating custom provider/base-URL configuration and persistent thread stategpt-5.6-solReply 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:
No email, account/workspace ID, token, thread ID, hostname, IP address, username, or local path is included.
lidge-jun commentedon Aug 16, 2026
Fixed on
devby #1848 (merged as73440d4bb).The classifier mapped every 403 to
credential, which calledmarkAccountNeedsReauth. 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 undererror) matching an allowlist — includingcodex_workspace_access_denied— yieldsdenial: "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
devand ships with the next release.fix(codex): stop calling a workspace denial a credential failure
jojodat commentedon Aug 17, 2026
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 returneddenial = nullbefore the patch. After adding an own-property check fordetail.code, it returneddenial = "workspace", and a local hotfix removed the incorrectneeds-reauthstate.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:
detail.code.needs-reauth.No additional private data should be necessary. If a diagnostic build is needed, I can offer one anonymized K12 test run.
fix(codex): workspace 拒绝账号立即软回避,修复个别任务反复落到坏账号
fix(codex): workspace 拒绝账号立即软回避,修复个别任务反复落到坏账号
fix(codex): stop calling a workspace denial a credential failure