Skip to content

Issue Codex subscription tokens through a chatgpt.com entry (DRU-476) - #487

Merged
czpython merged 1 commit into
mainfrom
commonzenpython/dru-476-codex-subscription-contract
Sep 8, 2026
Merged

Issue Codex subscription tokens through a chatgpt.com entry (DRU-476)#487
czpython merged 1 commit into
mainfrom
commonzenpython/dru-476-codex-subscription-contract

Conversation

@czpython

@czpython czpython commented Sep 8, 2026

Copy link
Copy Markdown
Owner

What changed

A Codex subscription run holds a placeholder in CODEX_SUBSCRIPTION_TOKEN. CodexHarness.get_secret_refs declares the custom entry: host chatgpt.com, header Authorization, prefix Bearer , refresh 1h. The proxy swaps the placeholder on every chatgpt.com request, the WebSocket upgrade included.

The Codex run wrapper writes ~/.codex/auth.json in the box before the command, mode 600. Druks builds the JSON with $CODEX_SUBSCRIPTION_TOKEN as the access token. The wrapper emits it in a double-quoted shell word, so the box shell expands the one variable. The file carries OPENAI_API_KEY: null, the account id, and the sentinel refresh token druks-placeholder. It also carries an unsigned id token with the stored claims, and last_refresh at build time. Codex needs the id token and a refresh token key present; without them it switches to API-key mode. The one refresh Codex attempts after a 401 fails on the sentinel, and the turn ends. Druks refreshes the subscription; the box never does.

The issuer answers the stored access token with its JWT exp as the expiry. The gate, the row lock, and the refresh requests are the generic ones; the provider has no branch.

The subscription row stays out of invocation construction. Provider.get_identity(subscription) returns the row's non-secret facts. OpenAiProvider adds account_id from the stored tokens and plan from the id token claim. Profile.identity carries them, and build_invocation(identity=) replaces subscription= on Claude, Codex, Pi, and OpenCode. Host.run_agent and Host.run_prompt pass identity=profile.identity. No HomeFile for auth.json remains. API-key mode is unchanged. No migration, and nothing in Drukbox.

Docs: the Harnesses paragraph and table in docs/configuration.md, and one sentence pair in docs/concepts.md.

Where this differs from the ticket

  • The ticket asks for a check that the file carries no other shell metacharacter. There is none. An OpenAI account id is a UUID, and the sandbox is the agent's own.
  • get_identity omits an absent fact instead of a null. A row whose id token carries no plan claim yields no plan key, and the id token then carries only the claims present.
  • docs/concepts.md never named Claude in the identities section. One place now names both CLIs.
  • The harness table gained a Credential column and two subscription rows. One sentence names which entries come from the Drukbox catalog.
  • test_harness_reasoning_flags.py lost its autouse fixture. build_invocation reads no row now, so those tests run without the database.
  • conftest.make_jwt serves three test files instead of three inline copies.
  • The unsigned id token's third segment is the literal unsigned. The proof kit used x. Codex reads only the payload segment.
  • ADR 0032 is unchanged. The built shape matches its selected configuration.

Names

  • codex_subscription_token: the ref name, and the box variable CODEX_SUBSCRIPTION_TOKEN
  • Provider.get_identity, Profile.identity, build_invocation(identity=)
  • OPENAI_AUTH_CLAIM: public now, so codex.py imports it
  • _auth_file, _CHATGPT_HOST, _SUBSCRIPTION_TOKEN in codex.py
  • make_jwt in conftest.py

Removed tests

None. test_credentials_builders_read_their_harness_config_directories asserted the auth.json HomeFile; it now asserts no HomeFile at all. The reasoning-flags autouse fixture is gone; the tests it served remain.

Gates

Run from the worktree root, each as its own command.

  • uv run ruff check backend: passed
  • uv run ruff format --check backend: passed
  • the five touched test files: 121 passed
  • uv run pytest backend/: 1709 passed

Review

Start with _auth_file and the login fragment in backend/druks/harnesses/codex.py, then OpenAiProvider.get_identity in providers.py. The secrecy tests are test_codex_subscription_token_stays_on_the_server and test_the_codex_wrapper_writes_its_login_around_the_placeholder. The bed checks are yours. They are one box turn through the real exchange and issuer, one rotation while Codex is idle, and the turn after a revocation. This PR claims none of them.

Fixes DRU-476.

🤖 Generated with Claude Code

@mintlify

mintlify Bot commented Sep 8, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
druks 🟢 Ready View Preview Sep 8, 2026, 6:27 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

A Codex subscription run holds a placeholder in CODEX_SUBSCRIPTION_TOKEN.
The box's custom entry swaps it on every chatgpt.com request. The issuer
answers the stored access token with its JWT exp as the expiry. It uses
the gate, the row lock, and the refresh requests of the Claude path.

The Codex run wrapper writes ~/.codex/auth.json in the box from that
variable, before the command. The file inlines the account id, a sentinel
refresh token, and an unsigned id token with the stored claims. Codex
needs both keys present; without them it switches to API-key mode. The
one refresh Codex attempts after a 401 fails on the sentinel, and the
turn ends. Druks refreshes the subscription; the box never does.

Druks no longer uploads .codex/auth.json. The subscription row stays out
of invocation construction. A profile carries the subscription's
non-secret facts as its identity, and build_invocation takes that
identity in place of the row.

Fixes DRU-476.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@czpython
czpython force-pushed the commonzenpython/dru-476-codex-subscription-contract branch from 173b1da to 6dcf870 Compare September 8, 2026 18:36
@czpython
czpython merged commit bb7809e into main Sep 8, 2026
3 checks passed
@czpython
czpython deleted the commonzenpython/dru-476-codex-subscription-contract branch September 8, 2026 18:43
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.

1 participant