Skip to content

feat: accept full ChatGPT session JSON for Codex manual import (#6636)#7725

Merged
diegosouzapw merged 2 commits into
release/v3.8.49from
feat/6636-codex-session-import
Jul 19, 2026
Merged

feat: accept full ChatGPT session JSON for Codex manual import (#6636)#7725
diegosouzapw merged 2 commits into
release/v3.8.49from
feat/6636-codex-session-import

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Summary

The Codex OAuth modal's manual-paste handler only recognized a bare JWT (/^eyJ/). Pasting the raw JSON object copied straight from https://chatgpt.com/api/auth/session ({user, accessToken, expires}) fell through to the OAuth-code/URL parser and errored out — exactly the failure reported in #6636.

  • Added src/lib/oauth/utils/codexSessionImport.ts — a pure, unit-testable normalizer (parseCodexSessionJson / looksLikeCodexSessionJson) that extracts the access token from the exact chatgpt.com/api/auth/session shape plus common aliases (access_token, sessionToken, session_token, nested tokens.access_token), and rejects malformed/tokenless/expired input with a typed, actionable error.
  • Wired OAuthModal.tsx's handleManualSubmit Codex branch to detect a pasted JSON object and route it through the normalizer before falling through to the OAuth-code parser. Extracted the existing bare-JWT fetch logic into a small shared submitCodexAccessToken() helper reused by both branches (same pattern as the oauthBlobSubmit.ts extraction from feat(oauth): remote Antigravity login via local helper + paste-credentials #5203) — OAuthModal.tsx is a frozen god-file (config/quality/file-size-baseline.json), so all new normalizer/parsing logic lives in the new sibling module; only wiring lives in the modal itself. The modal's frozen line budget grew 998->1030 (gate units) and required a rebaseline entry (_rebaseline_2026_07_19_6636_codex_session_json) since this is a genuine new branch, not pure wiring.
  • Hardened POST /api/oauth/codex/import-token (Step 3, shipped) to accept either the existing { accessToken: string, name?: string } body or { session: {...}, name?: string } for non-UI/API callers, reusing the same normalizer. Error responses continue to go through buildErrorBody().

Validation (TDD, Hard Rule #18)

New tests were written first — they fail on origin/release/v3.8.49 (the codexSessionImport.ts module doesn't exist there) and pass after the fix:

  • tests/unit/codex-session-json-import-6636.test.ts (new, 10 tests) — normalizer field-alias detection, expired-session rejection (expires field and JWT exp claim), malformed/non-object rejection, tokenless rejection, non-JWT token rejection, and looksLikeCodexSessionJson shape-guard cases.
  • tests/unit/codex-import-token-route.test.ts (extended, +2 tests) — the route accepts a full session-JSON body end-to-end and creates an identical connection to the bare-accessToken path; an expired session-JSON body is rejected with a 400.
node --import tsx/esm --test tests/unit/codex-session-json-import-6636.test.ts tests/unit/codex-import-token-route.test.ts
# 21 pass, 0 fail

Gates run (all green)

  • node scripts/check/check-test-discovery.mjs
  • npm run typecheck:core
  • npm run typecheck:noimplicit:core (pre-existing unrelated failures in combo.ts/usageTracking.ts/cliRuntime.ts confirmed present on origin/release/v3.8.49, untouched by this PR)
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>
  • npm run check:complexity-ratchets (required extracting parseRequestBody()/resolveAccessToken() in the route to keep POST's own cyclomatic complexity flat as the accepted body shapes grew)
  • node scripts/check/check-file-size.mjs (OAuthModal.tsx rebaselined 998->1030 with a dated, cited justification comment, following the feat(oauth): remote Antigravity login via local helper + paste-credentials #5203/feat(providers): add xAI OAuth PKCE provider #7399 precedent)
  • npm run check:cycles
  • npm run check:docs-sync

npm run test:coverage was not run locally (heavy gate, shared box) — the change is additive-only: a new pure module with its own full test file, plus 2 new route tests; no existing assertion was removed or weakened. CI runs the coverage gate authoritatively.

Deferred

  • The plan's Step 6/manual-verification item — a live VPS smoke test importing a real chatgpt.com/api/auth/session JSON to confirm the token has sufficient backend-api scope through the executor — was deferred: no live ChatGPT session was available in this session. This is the plan's own stated residual risk ("a chatgpt.com web-session accessToken may carry narrower scopes than the Codex CLI's PKCE-issued token"); the server-side storage/executor path is unchanged from the already-shipped bare-token import ([BUG] compressContext() is implemented but never integrated into chatCore.ts request pipeline #1290, authType: "access_token"), so this widens only which pasted shapes reach that existing, working path.

Closes #6636

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@diegosouzapw
diegosouzapw merged commit 2741cc5 into release/v3.8.49 Jul 19, 2026
13 of 20 checks passed
@diegosouzapw
diegosouzapw deleted the feat/6636-codex-session-import branch July 19, 2026 21:00
diegosouzapw added a commit that referenced this pull request Jul 19, 2026
… check (base-red unblock)

#7725 landed 'if (!result.ok)' in OAuthModal — under this repo's strict:false,
tsc 6 only narrows a discriminated union on the equality form, so the negation
raised TS2339 (Property 'error' does not exist on ParseResult) and turned the
dashboard-typecheck gate red for every PR on release/v3.8.49. Runtime semantics
are identical (ok is a strict boolean).

Also ratchets the frozen baseline down 260 -> 259: the real fix here plus 3
baselined errors that other merges already fixed (CostOverviewTab TS2304,
SidebarTab TS2322, FreePoolTab TS2304). Baseline diff is deletions-only.
diegosouzapw added a commit that referenced this pull request Jul 19, 2026
…rift again (#7798)

* docs(free-tiers): correct the headline to the 1.37B the catalog actually computes

The 2026-06-17 honesty correction landed 1.54B, but v3.8.42 reclassified longcat
from a 150M/mo recurring grant to a one-time 10M signup credit and the doc was
never resynced. Verified against computeFreeModelTotals() at every release tag
from 3.8.13 to HEAD: no free provider was lost by mistake.

* feat(quality): gate the free-tier headline against the live catalog

The README headlined ~1.6B free tokens/mo for seven releases after the catalog had
already been corrected down to 1.37B. No gate watched that number, so the drift was
invisible — check:docs-counts only covered providers, locales, executors, strategies,
oauth, a2a skills and cloud agents.

Adds a STRICT check that runs computeFreeModelTotals() (the same function behind
/api/free-tier/summary) and fails the build when README.md or FREE_TIERS.md publish a
headline that no longer rounds to it. Degrades to a skip if tsx is unavailable rather
than going falsely red.

The extractor is a whitelist: the theoretical ceiling (~10B), the historical ~1.94B and
per-model rows (~1.00B) are legitimate figures that must never trip the gate.

Also adds the biweekly-audit note under the README headline, so readers know the number
moves both ways and is what the catalog computes rather than a rounded-up best case.

* feat(quality): extend the counts gate to engines, MCP tools/scopes and CLI tools

The v3.8.49 audit found four more numbers that had silently drifted, all invisible to
CI because check:docs-counts only watched providers/locales/executors/strategies/oauth/
a2a/cloud-agents: 10->11 compression engines, 94->104 MCP tools, 30->31 scopes,
26->33 CLI tools.

Adds a generic makeNumberClaimValidator that reads every fact in ONE tsx subprocess via
the same functions the app serves (ENGINE_IDS, countUniqueMcpTools, the live scope union,
CLI_TOOLS) — never a hardcoded copy — with DATA_DIR redirected to a throwaway dir so
importing the MCP tool modules can't touch the operator's SQLite. Each check declares a
skip pattern so legitimate non-aggregate figures never trip it: per-module tool counts
('Memory tool definitions (3 tools)') and the CLI catalog total sitting next to the MCP
total. Degrades to a skip when tsx is unavailable rather than a false red.

7 new unit tests (all pass) covering the exact stale values this audit found and proving
per-module counts are ignored.

* docs(diagrams): sync the animated cards and mermaid sources to the audited v3.8.49 numbers

The README text was fixed in #7795 but the SVG cards and mermaid sources kept the
old numbers baked in — exactly the drift the readers see first.

- compression-pipeline.svg: 10 -> 11 engine cells (Omniglyph added as #8, matching
  the README alt text), re-spaced 51px cells, highlight cascade re-timed, the
  Caveman kill-dot repositioned inside its cell, default-stack bracket recentered
- free-tier-budget.svg: bar and grid rebuilt from computeFreeModelTotals() — 21 -> 19
  countable pools (LongCat-2.0 moved to one-time credit, Inclusion provider removed),
  huggingchat entry is now ERNIE 4.5 VL, kiro shows Claude Sonnet 4.5, signup credits
  ~616M -> ~626M (+longcat 10M pill), aria said 'about 1.6 billion' -> 1.4/2.0,
  lower sections shifted up 30px (viewBox 872 -> 842)
- promise-pillars.svg: 26 -> 33 coding agents
- mcp-tools-94.mmd -> mcp-tools-104.mmd: real per-collection unique contributions
  (42 base + memory 3 + skill 4 + githubSkill 3 + pool 6 + gamification 8 + plugin 8
  + notion 6 + obsidian 22 + compression 2), exported SVG regenerated, zh-CN ref synced
- request-pipeline.mmd: 17 -> 18 strategies, exported SVG regenerated
- README free-tier alt + docs/diagrams/README.md synced to the same numbers

Both edited cards pass validate-svg.sh and were render-verified at 4 timestamps
(animation runs; first frame is the finished composition).

* docs(env): register the 4 env vars missing from the .env.example contract (base-red unblock)

FREE_PROXY_AUTO_SYNC_ENABLED / FREE_PROXY_AUTO_SYNC_INTERVAL_MS (scheduler.ts) and
MITM_ROOT_CA_ENABLED / MITM_CERT_MODE (mitm manager/server, #6684) landed on
release/v3.8.49 without their .env.example + ENVIRONMENT.md entries, turning the
docs-gates job red for every PR on the branch. Documented with their real defaults
and the set-by-manager caveat for MITM_CERT_MODE.

* fix(dashboard): narrow the Codex session ParseResult with an equality check (base-red unblock)

#7725 landed 'if (!result.ok)' in OAuthModal — under this repo's strict:false,
tsc 6 only narrows a discriminated union on the equality form, so the negation
raised TS2339 (Property 'error' does not exist on ParseResult) and turned the
dashboard-typecheck gate red for every PR on release/v3.8.49. Runtime semantics
are identical (ok is a strict boolean).

Also ratchets the frozen baseline down 260 -> 259: the real fix here plus 3
baselined errors that other merges already fixed (CostOverviewTab TS2304,
SidebarTab TS2322, FreePoolTab TS2304). Baseline diff is deletions-only.

* fix(dashboard): keep OAuthModal within the frozen file-size cap

The narrowing comment pushed the file to 1032 > 1030 frozen; the rationale lives
in the previous commit message and the dashboard-typecheck gate itself guards the
'=== false' form from being refactored back to '!result.ok'.

* test(providers): align the grok-web credential assertion with the #7567 hint (base-red unblock)

#7713 added hintKey/hintFallback (proactive cf_clearance/User-Agent guidance) to the
grok-web web-session metadata without touching this test's deepEqual, turning unit
shard 2/4 red for every PR on release/v3.8.49. Rewritten in the same contract-only
style the file already uses for lmarena: structural fields stay strictly asserted,
the hint asserts key + intent (cf_clearance / User-Agent) without freezing operator
copy. Net stronger than before — the old assertion never checked the hint at all.

* test(golden): regenerate translate-path snapshot for the notion-web endpoint move (base-red unblock)

#7768 switched notion-web to app.notion.com without regenerating the golden,
turning unit shard 3/4 red for every PR on release/v3.8.49. Two-line regen,
reflects the deliberate production change.
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.

feat(auth): import Codex auth from ChatGPT session JSON (skip SMS re-auth)

1 participant