Skip to content

fix(codex): serialize native-main refresh on the CODEX_HOME claim (rebase of #3112) - #3183

Merged
lidge-jun merged 4 commits into
devfrom
codex/3112-claim-serialize
Sep 1, 2026
Merged

fix(codex): serialize native-main refresh on the CODEX_HOME claim (rebase of #3112)#3183
lidge-jun merged 4 commits into
devfrom
codex/3112-claim-serialize

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Maintainer rebase of #3112 onto current dev — all four commits cherry-picked with author credit preserved, no conflicts.

Serializes native-main refresh on the CODEX_HOME claim so two OpenCodex instances sharing one Codex home cannot both publish a refreshed credential. Addresses the lock-scope half of #2999.

Scope note, stated plainly: this does not close #2999. That issue describes two races. This is the serialization half; the publication/overwrite race remains carried by the existing refuse-rather-than-overwrite check. #2999 stays open with that scope recorded.

Review threads

The CHANGES_REQUESTED state came from bot reviewers against commit a6014b9688, which is four commits behind this head. The one substantive finding was resolved by a later commit on the same branch:

P2 — Make claim waiting honor the refresh abort signal. "this outer wait continues polling SQLite for up to 30 seconds because only the inner fingerprint lock receives signal"

src/codex/main-account.ts now passes { waitMs: 30_000, signal } into the claim wait, where signal is AbortSignal.any([dependencies.signal, refreshTimeout]) — the caller's abort combined with the refresh timeout. That is exactly what the review asked for, delivered by "abort contended native-main refresh claims".

Verification

Exact head eaa8b8463:

  • bun test ./tests/codex-main-account-refresh.test.ts — 5 pass, 0 fail
  • bun test ./tests/native-main-claim.test.ts — 8 pass, 0 fail
  • bun test ./tests/responses-native-main-refresh.test.ts — 8 pass, 0 fail
  • bun test ./tests/responses-compaction-routing.test.ts — 47 pass, 0 fail

One pre-existing parallel flake, verified not ours. Running those files together with tests/codex-auth-context.test.ts in one invocation fails Codex auth context > guardian and quota consumers back off and retry a busy credential refresh without reauth at ~4.9s — a file-lock contention timeout. The identical failure reproduces on clean origin/dev with the same file set and the same ~4.9s duration, and every file passes alone. It is a suite-parallelism artifact in a credential-refresh test that takes real locks, not fallout from this diff.

Checklist

Reimplements #3000 (author @MarcTCruz) for the #2999 lock-scope half.

The refresh lock is keyed on the grant fingerprint and lives under
OPENCODEX_HOME (src/codex/account-store.ts:420-422, via getConfigDir).
The file it protects is auth.json under CODEX_HOME, which every OpenCodex
install on the machine shares regardless of its own home. Two proxies with
distinct OPENCODEX_HOMEs therefore took two unrelated locks and refreshed the
one credential concurrently; the loser published its rotated grant over the
winner's and the provider then rejected it.

The outer lock is now withNativeMainExclusiveClaim on
resolveNativeProfileContext(), which is the CODEX_HOME coordination the other
native-main paths already use (.opencodex-native-main.claim.sqlite). No new
primitive, no FFI.

Why not #3000's approach: it introduces
src/lib/atomic-file-preserving-replace.ts, which dlopens libc.so.6 /
libSystem.B.dylib / kernel32.dll for renameat2 / renamex_np / ReplaceFileW and
throws "No rename fallback is safe" on anything else. musl names its libc
libc.so, not libc.so.6, so publication would crash on Alpine. It also throws
MainAccountTokenRefreshError("transient") on an aborted signal BEFORE
persistRefreshedMainAuthJson, so a late cancel discards a grant the provider
has already rotated -- the only live refresh token, dropped.

The existing check-then-rename guard is left as it is: atomicWriteFile with
assertMainAuthJsonSnapshotUnchanged in both beforeRename and
validateBeforeRename refuses rather than overwrites, and the covering test
(refuses to overwrite an external auth writer after refresh) already passes.

Lock order is claim (machine-wide) then fingerprint lock (per-grant), never the
reverse: two processes holding different fingerprint locks and then reaching
for the same claim would deadlock.

Mutation-checked: dropping the claim wrapper fails exactly the new test
(3 pass / 1 fail), restored to 4/0.

Closes #2999.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 1, 2026 17:22
@lidge-jun
lidge-jun merged commit fecb77a into dev Sep 1, 2026
7 checks passed
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T17:27:32.832459Z eaa8b84 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lidge-jun
lidge-jun deleted the codex/3112-claim-serialize branch September 1, 2026 17:22
@github-actions github-actions Bot added the bug Something isn't working label Sep 1, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eaa8b84634

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codex/main-account.ts
Comment on lines +211 to +212
return await withNativeMainExclusiveClaim(
resolveNativeProfileContext(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve refreshes on unsupported claim filesystems

When Codex integration startup is disabled (for example, clientIntegrations.codex: false or runtimeRole: "hub"), proxy routes can still use the native-main credential without the startup owner gate. On WSL with CODEX_HOME under /mnt/<drive> or on a Windows UNC home, nativeMainOwnerFilesystemSupported rejects this claim, so the new unconditional wrapper throws NATIVE_MAIN_CLAIM_UNAVAILABLE before invoking the refresh callback and every expired credential returns 503 indefinitely. Preserve the prior refresh path on these supported CODEX_HOME layouts or provide a coordinator that works on them.

AGENTS.md reference: src/AGENTS.md:L10-L11

Useful? React with 👍 / 👎.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant