Flow A 403 Cases — Every Failure and What It Taught
The complete catalog of 403s encountered while closing flow A — the exact causes, per-case evidence, and the lessons that led to the working sequence
Flow A 403 Cases — Every Failure and What It Taught
Part of openai-research. Each case below was observed live with captured evidence; each is a distinct cause (or a validated non-cause). The final column is what the failure established.
The 403 catalog
| # | What was attempted | Response | Root cause (proved by) |
|---|---|---|---|
| 1 | Challenge with a fresh generated did (02987bd6 / 04bd1580) | 403 | The did is installation identity (persisted, never regenerated). Proof: same token, did a05f9cdd → 200. The server binds oai-did↔device; an unknown did dies at the challenge. |
| 2 | /f/conversation with the PKCE-session access (any run of the A0 line) | 403 "Unusual activity" | The session created by a manual browser authorize is not recognized by the edge for f/*; only the app-server session (auth.json/live authToken) passes. Proof: A/B (the same request with app-session access → 200). |
| 3 | /f/conversation with x-conduit-token | 403 | The app's own winning requests carry no conduit header (the conduit is prepare-only; the cluster pin it carries was the suspected differentiator — and its absence is what the app does). Proof: app request 200 without it; our request without it → 200. |
| 4 | Challenge computed over node:https while the conversation goes via the main (A-1 step) | 403 | Same-path invariant: challenge+prepare+conv must come from the same transport/pool (the app always uses the main). Fix: everything through the conv trigger. |
| 5 | local_function_signatures without the handoff (only the custom tool) in a normal turn | 403 | The server expects the handoff signature in normal conversation turns (the app always sends it; 100% of captured 200s include it). |
| 6 | Continuation (2nd text turn) without parent_message_id | 403 | The app always supplies conversation_id + parent_message_id on follow-up turns (verified in captured payloads). |
| 7 | Re-executing a request (curl re-run of the same /run/conv input) | 403 | The app_attest_challenge is one-time; reuse of the same input/request → 403 (and the earlier verified 200 was overwritten by my own re-run). |
| 8 | /devicecheck with a revoked PKCE access (after the user killed the sessions) | 401 token_revoked | The access token had been revoked (the earlier PKCE sessions were logged out); not a protocol issue. |
| 9 | (Non-403, but a lesson)static auth.json after an app-server refresh | — | The live getAuthStatus {includeToken:true} returns the current session token; the disc file can lag (a refresh at 16:11 changed the file and all requests with the stale access became 403 until the live token was used). |
The two broken chains that caused most of the damage
- "Own session" chain (A0): the A0 probe created its own PKCE session, used a regenerated did, and attached the conduit — three deviations from the app in one request → 403 stack (cases 1–3). The system closed it: the session must be the app-server's, the did must be the installation's, and the conduit must not travel.
- Mixed-transport chain (A-1): the probe fetched the challenge over its own HTTP while the conversation went through the main (case 4) → the challenge↔conversation pairing is source-bound.
What the failures established (the final set of invariants)
oai-did: persisted installation identity (live renderer storage; never regenerate).- Session access: the live app-server
authToken(getAuthStatusincludeToken) — not a fresh login, not the stale file. - Transport: challenge+prepare+conv through the same main/electron path.
- No
x-conduit-tokenon/f/conversation. local_function_signatureswith the handoff present in normal turns.conversation_id+parent_message_idon every turn after the first.app_attest_challengeis one-time per request (never reuse/re-run an input).
Cross-references
- flow-a-manual-step-by-step — the sequence that passes
- flow-a-tool-protocol — the tool-return contract (equally validated)
- flow-definitions — where A0/A1/B sit
- attestation-flow — registration/challenge mechanism
- postmortem-flow-a-2026-08-27 — the full narrative of the cycle (including the process failures behind these cases)