Skip to content

fix(server): renew managed Codex credentials during active turns - #23051

Closed
lukemavdynamics wants to merge 1 commit into
pingdotgg:mainfrom
lukemavdynamics:fix/codex-managed-token-renewal
Closed

lukemavdynamics wants to merge 1 commit into
pingdotgg:mainfrom
lukemavdynamics:fix/codex-managed-token-renewal

Conversation

@lukemavdynamics

@lukemavdynamics lukemavdynamics commented Oct 21, 2026 •

Copy link
Copy Markdown

Summary

Managed ChatGPT credentials can renew in T3 while an existing Codex app-server keeps its launch-time bearer. Subsequent requests then fail with 401 token_expired, and the provider fallback can still report expired credentials as authenticated.

 managed Codex authentication
- ACCESS_TOKEN captured when the process starts
+ native auth.command → profile-bound T3 helper → existing locked credential renewal

The hidden token helper reads and renews the selected profile through the existing protected credential store and interprocess lock. Profile binding is checked inside that lock before renewal or deletion. The helper returns no token after sign-out or profile replacement. The child environment no longer contains the owned bearer. Expired credentials retained after a failed check report unknown instead of authenticated, allowing renewal to be retried.

Addresses the failure and intended behavior established in #17445 and #16132. This is an alternative to the unmerged #21787, using the existing credential store directly for the native helper.

Evidence

  • Before: the status regression returned authenticated for expired saved credentials; the runtime supplied a static bearer.
    After: the regression reports unknown, and managed sessions use native command-backed authentication.
  • Profile replacement: the helper refuses the old profile before renewal or deletion. The regression verifies that expired replacement credentials remain stored, including when they have no refresh token.
  • Native recovery on Linux: real Codex 0.155.1 and 0.163.0 used the actual T3 helper with isolated dummy credentials and synthetic OAuth/inference responses. An inference 401 triggered one OAuth refresh, persisted the rotated refresh token, and completed the same turn in the same process with two inference attempts.
  • vp run check:changed: format, lint, Knip, generated GraphQL, both affected typecheck packages, and 80 tests in five files passed. Lint reports one existing empty-pattern warning elsewhere in the driver test file.
  • The shared self-invocation and text-generation suites also passed: 22 tests. Credential coverage includes rotation, transient renewal failures, sign-out and profile replacement. git diff --check passes.
Native OAuth recovery at commit 9b2528e
codex: codex-cli 0.163.0
exit: 0
helper: T3 __codex-token invocations: native retry
inference attempts: 2
OAuth refresh exchanges: 1
rotated refresh token persisted: True
credential changed on retry: True
completed same turn: True

The actual T3 token command uses an isolated credential store. Synthetic OAuth responses exercise the real renewal and persistence path; the inference fixture returns 401 followed by 200.

Merge Danger

Door: two-way. No persistent format or schema migration.

Blast Radius: Authentication.

Applies to T3-managed Codex ChatGPT sign-in. Linux source execution and synthetic provider responses were verified. Real-account hour-long expiry, the affected hoss installation, macOS/Windows, and packaged Electron/SEA were not exercised. A manually configured token-sharing env_key in shared Codex configuration conflicts with native auth.command.

Cross-model review: NOT RUN. T3 isolated delegation could not resolve a parent checkout from this scratch thread. The read-only Claude Opus 5.5 fallback exited with “Autocompact is thrashing” before returning findings.

Credential lifecycle uses the existing sign-in, protected reads, locked renewal and sign-out removal.

Implemented with GPT-6.1-Sol through the Codex harness in T3 Code.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Oct 21, 2026
Comment on lines +44 to +50
const tokenArguments = selfInvocationArgs(invocation, [
"__codex-token",
"--base-dir",
config.baseDir,
...(config.devUrl ? ["--dev-url", config.devUrl.toString()] : []),
"--instance-id",
options.instanceId,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High provider/CodexManagedRuntime.ts:44

When the server uses an implicit dev home, this child invocation selects <baseDir>/userdata instead of the parent’s <baseDir>/dev, so auth.access looks in the wrong secret store and managed Codex authentication fails. Forwarding --base-dir makes the home explicit even though --dev-url is also forwarded; remove --base-dir so the child preserves the parent’s implicit state-directory selection.

Suggested change
const tokenArguments = selfInvocationArgs(invocation, [
"__codex-token",
"--base-dir",
config.baseDir,
...(config.devUrl ? ["--dev-url", config.devUrl.toString()] : []),
"--instance-id",
options.instanceId,
const tokenArguments = selfInvocationArgs(invocation, [
"__codex-token",
...(config.devUrl ? ["--dev-url", config.devUrl.toString()] : []),
"--instance-id",
options.instanceId,
]);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/CodexManagedRuntime.ts around lines 44-50:

When the server uses an implicit dev home, this child invocation selects `<baseDir>/userdata` instead of the parent’s `<baseDir>/dev`, so `auth.access` looks in the wrong secret store and managed Codex authentication fails. Forwarding `--base-dir` makes the home explicit even though `--dev-url` is also forwarded; remove `--base-dir` so the child preserves the parent’s implicit state-directory selection.

@macroscopeapp

macroscopeapp Bot commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The PR changes the default managed Codex authentication path from a static bearer to a child-process credential-renewal workflow, affecting sensitive production runtime behavior. An unresolved high-severity finding also concerns incorrect credential-store selection in implicit development mode.

Needs human approval because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant