Skip to content

[AGE-4063] fix(runner): rebuild the Codex subscription auth symlink on every durable mount - #5696

Merged
mmabrouk merged 3 commits into
release/v0.108.1from
fix/codex-subscription-auth-symlink-5692
Aug 3, 2026
Merged

[AGE-4063] fix(runner): rebuild the Codex subscription auth symlink on every durable mount#5696
mmabrouk merged 3 commits into
release/v0.108.1from
fix/codex-subscription-auth-symlink-5692

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Aug 3, 2026

Copy link
Copy Markdown
Member

Context

Running a Codex agent with an operator ChatGPT subscription works on the first turn of a session and fails on every turn after that, with codex: model authentication failed — add the project's OpenAI key to the project vault, or log in (OAuth). The login on the host is healthy the whole time.

The Codex home lives at <cwd>/.codex, on the durable session working directory. Whenever the object store is configured (the default self-hosted setup) that directory is a geesefs FUSE mount over S3, and S3 has no symlinks. The auth.json symlink the first turn creates flushes to the store as a 0-byte object and comes back from the next mount as an ordinary empty file. symlinkCodexSubscriptionAuthFile guarded with existsSync, read that degraded entry as "the link is already there", and returned early. Codex then read an empty token file, and the harness reported it as an auth failure. Once a working directory held that object it stayed broken forever.

Changes

The repo already had the right pattern for exactly this hazard. linkAgentFiles keeps <cwd>/agent-files pointing at the agent mount by lstat-ing the path, keeping only a symlink whose readlink matches the wanted target (including a dangling one), and replacing anything else. Its comment says geesefs silently degrades symlinks to empty files across remounts.

That logic now lives once, in ensureDurableSymlink (durable-symlink.ts). linkAgentFiles delegates to it, so its behavior and log lines are unchanged, and symlinkCodexSubscriptionAuthFile uses it in place of existsSync.

Before, the guard was:

if (existsSync(linkPath)) return;   // a 0-byte file passes this
symlinkSync(target, linkPath);

Now the entry has to actually be a symlink to the operator's login, or it gets replaced. Sessions that already hold the degraded file self-heal on their next turn, with no manual cleanup on upgrade.

Two smaller things ride along.

The link is now attached to the durable-mount lifecycle instead of only to the acquire path. mountLocalAgentCwd already re-ran linkAgentFiles on every mount; its sibling mountLocalDurableCwd did not re-run the Codex link, so a mid-session remount (the ENOTCONN recovery path) left the session authenticating from whatever the store handed back. The link is created after the mount is active, never before, and stays a no-op for Daytona runs, managed-key runs, and non-Codex runs. The acquire-path call remains for the run that has no durable mount at all.

The error message no longer misdirects. When a subscription run hits the auth branch, conciseError consults an optional authFault callback, and the Codex path checks the token file the run actually reads. If it is empty or unreadable the operator now sees: codex: the mounted ChatGPT login is empty or unreadable — this run authenticates from the CODEX_HOME mount, not a project key. Sign in again on the host, then retry. The check is lazy, so it only runs on the error path it explains, and the message carries no credential material.

The Codex home stays on the durable cwd, so native rollouts still persist and durable resume and warm daemon reuse are untouched. The token is never copied into the working directory, and nothing writes to or deletes the operator's mounted login. Teardown still only ever removes the session-local symlink.

Tests / notes

  • tests/unit/durable-symlink.test.ts covers the shared helper against the real filesystem (0-byte file replaced, wrong target replaced, correct link kept including while dangling) plus the injected failure paths.
  • sandbox-agent-codex-assets.test.ts covers the same three cases through symlinkCodexSubscriptionAuthFile, asserts the operator's own login file is untouched while self-healing, and covers the auth-fault diagnosis (empty, unreadable, healthy, and the modes it must stay silent for). The old test that asserted a pre-existing plain auth.json is left alone encoded the bug and is replaced.
  • sandbox-agent-orchestration.test.ts drives a full subscription Codex run: one test simulates the store handing back a 0-byte file on a mid-prompt remount and asserts the link is a symlink again afterwards, another asserts a 401 with an empty mounted login surfaces the new message and never mentions the vault. The remount test fails against the pre-fix mount path, so it pins the gap it closes.
  • pnpm test (1496 passed, 97 files) and pnpm run typecheck both pass from services/runner.

What to QA

  • Self-hosted stack with the object store enabled, Codex harness, self-managed credential, local sandbox: send a first message, then a second in the same session. Both turns should succeed, and the runner log should show the codex subscription auth.json symlinked line on each mount rather than only on the first turn.
  • A session that is already broken from before this change (its stored .codex/auth.json is a 0-byte object) should recover on its next turn without touching the store by hand.
  • Regression to watch: managed-key Codex runs stay file-free, and no auth.json should appear under <cwd>/.codex for them. Claude and Pi subscription runs point their config var straight at the mount and should be unaffected.

Closes #5692

…able mount

The durable session cwd is a geesefs mount over object storage, which has no
symlinks: `<cwd>/.codex/auth.json` flushes to the store as a 0-byte object and
comes back as an ordinary empty file. `symlinkCodexSubscriptionAuthFile`
guarded with `existsSync`, read that degraded entry as "already linked", and
returned early, so every turn after the first authenticated from an empty token
file.

Extract the lstat/readlink/replace logic `linkAgentFiles` already used for the
same hazard into `ensureDurableSymlink`, use it for both links, and attach the
Codex link to the durable-mount lifecycle so a mid-session remount
re-materializes it. A subscription run that still fails auth now says the
mounted login is empty or unreadable instead of asking for a project key.

Closes #5692
@linear-code

linear-code Bot commented Aug 3, 2026

Copy link
Copy Markdown

AGE-4063

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. backend labels Aug 3, 2026
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 3, 2026 7:20pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 219b5447-f50f-4093-9383-c1977ef85d6f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of Codex subscription authentication links after durable environment remounts.
    • Automatically repairs missing, stale, incorrect, or unusable authentication links.
    • Added clearer error messages when the mounted ChatGPT login is missing, unreadable, or empty.
  • Tests
    • Added coverage for link recovery, authentication diagnostics, concurrent operations, and related failure scenarios.

Walkthrough

The runner now uses a shared durable symlink helper for agent mounts and Codex subscription authentication. It repairs links after durable-cwd remounts and reports specific errors for missing, unreadable, empty, or dangling subscription login files.

Changes

Codex authentication repair

Layer / File(s) Summary
Durable symlink helper
services/runner/src/engines/sandbox_agent/durable-symlink.ts, services/runner/tests/unit/durable-symlink.test.ts
Adds injectable symlink inspection, replacement, creation, logging, concurrency handling, outcomes, and filesystem tests.
Codex subscription auth-link repair
services/runner/src/engines/sandbox_agent/codex-assets.ts, services/runner/tests/unit/sandbox-agent-codex-assets.test.ts
Repairs stale or degraded auth.json entries asynchronously and classifies unusable mounted login files.
Authentication fault error contract
services/runner/src/engines/sandbox_agent/errors.ts, services/runner/tests/unit/sandbox-agent-errors.test.ts
Adds an optional lazy authFault callback to conciseError with generic-message fallback behavior.
Mount and turn integration
services/runner/src/engines/sandbox_agent/agent-mount.ts, services/runner/src/engines/sandbox_agent/environment.ts, services/runner/src/engines/sandbox_agent/run-turn.ts, services/runner/tests/unit/sandbox-agent-orchestration.test.ts
Uses durable symlink repair for agent mounts, recreates Codex auth links after remounts, and passes Codex-specific authentication diagnostics through environment and turn errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Environment
  participant DurableMount
  participant symlinkCodexSubscriptionAuthFile
  participant ensureDurableSymlink
  participant Codex
  DurableMount->>Environment: report successful local durable-cwd mount
  Environment->>symlinkCodexSubscriptionAuthFile: recreate subscription auth link
  symlinkCodexSubscriptionAuthFile->>ensureDurableSymlink: inspect and repair auth.json
  ensureDurableSymlink-->>symlinkCodexSubscriptionAuthFile: return link outcome
  Codex->>Environment: report authentication result
  Environment->>Codex: return specialized auth fault when login is unusable
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #5692 by repairing degraded auth links, restoring links after remounts, correcting errors, and preserving unaffected modes.
Out of Scope Changes check ✅ Passed The shared helper, error classification, lifecycle updates, and tests directly support the linked issue objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 60.00%.
Title check ✅ Passed The title clearly describes the primary fix: rebuilding the Codex subscription authentication symlink after each durable mount.
Description check ✅ Passed The description directly explains the authentication failure, implementation changes, lifecycle behavior, tests, and affected credential modes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/codex-subscription-auth-symlink-5692

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e2857e88-94ad-44aa-8a92-2731240f23c5

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9206e and 3bb9a00.

📒 Files selected for processing (10)
  • services/runner/src/engines/sandbox_agent/agent-mount.ts
  • services/runner/src/engines/sandbox_agent/codex-assets.ts
  • services/runner/src/engines/sandbox_agent/durable-symlink.ts
  • services/runner/src/engines/sandbox_agent/environment.ts
  • services/runner/src/engines/sandbox_agent/errors.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/tests/unit/durable-symlink.test.ts
  • services/runner/tests/unit/sandbox-agent-codex-assets.test.ts
  • services/runner/tests/unit/sandbox-agent-errors.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts

Comment thread services/runner/src/engines/sandbox_agent/durable-symlink.ts
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-03T19:30:03.010Z

@mmabrouk
mmabrouk changed the base branch from main to release/v0.108.1 August 3, 2026 19:08
…k-5692

The release branch regrouped RunPlan by concern and replaced the flat
top-level model credential fields with a resolved `modelConnection` object.
Three conflicts, all mechanical once both intents are kept:

- codex-assets.ts: keep this branch's async `ensureDurableSymlink` repair (no
  `existsSync` early return, which is the bug) and port it onto the grouped
  plan, so `symlinkCodexSubscriptionAuthFile` and
  `describeCodexSubscriptionAuthFault` take `CodexHomePlan` and read
  `plan.workspace.cwd`.
- environment.ts / run-turn.ts: keep the `authFault` callback this branch adds
  to `conciseError`, with the release branch's
  `request.modelConnection?.provider` as the provider argument.

The two codex tests this branch added still built requests and plans in the old
flat shape, which the release branch now rejects outright, so they move to
`modelConnection` and to `credentials`/`workspace`.

Runner suite green: 1559 tests, typecheck clean.
`ensureDurableSymlink` only logged a non-ENOENT unlink failure and carried on.
If the following symlink then threw EEXIST, it returned "linked" on the
assumption that a concurrent creator had won the race. But the same EEXIST is
what a degraded entry that could not be removed produces, so a transient EBUSY,
EACCES, or EIO on the FUSE mount left the 0-byte auth.json in place while the
caller logged a successful repair and the run kept failing authentication.

Track whether the unlink failed and treat EEXIST as benign only when it did not.

The existing unlink-failure test could not catch this: its injected symlink stub
always succeeds, so it never reaches the EEXIST a failed unlink would really
produce. Two tests added for the two EEXIST paths.

Refs #5692
@mmabrouk
mmabrouk merged commit fdd5675 into release/v0.108.1 Aug 3, 2026
56 of 57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(bug) Codex subscription auth fails on every turn after the first when the durable cwd is on the object store

1 participant