Skip to content

fix(codex): make history no-op detection atomic - #1294

Closed
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:agent/fix-history-migration-atomic-noop
Closed

fix(codex): make history no-op detection atomic#1294
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:agent/fix-history-migration-atomic-noop

Conversation

@luvs01

@luvs01 luvs01 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move history no-op authority into the existing Worker-held history serialization lock, after desired-state revalidation.
  • Prove an exact, present state database is empty using canonical request paths, strict backup-manifest schema and byte fingerprints, main-file identity, and a same-connection SQLite data_version fence that detects WAL commits.
  • Fail closed to the normal migration path for missing databases, malformed/foreign manifests, query uncertainty, path replacement, or any observed database/manifest generation change.
  • Carry a typed verified-noop proof across the Worker boundary, persist zero pending/backup counts only for that proof, and remove the guardian pre-count so only a verified Worker result can stop retrying.
  • Add deterministic regressions for WAL commits, proof forgery/path mismatches, missing-DB backup accounting, durable zero-vs-unknown counts, and guardian retry behavior.

Closes #1183.

Verification

  • Exact head cbd53046d0617ee4fed03c52d3bb56a5de3eb261 rebased and validated on dev@8e6d661b2340352219d5e3d694965a61244d91d1; live dev@a9838c1a353cab772265b65c14a473cde5c4441a was five disjoint commits ahead at publication time.
  • Bun 1.3.14: verified-noop Worker path 1/1 passed, 3 assertions.
  • Bun 1.3.14: provider, Worker-boundary, guardian, real Worker job, and uninstall integration set 48/48 passed, 210 assertions.
  • Bun 1.3.14: proof-backed and ordinary-zero transition mapping 2/2 passed, 7 assertions.
  • bun x tsc --noEmit: passed.
  • bun scripts/privacy-scan.ts: passed.
  • git diff --check 8e6d661b2340352219d5e3d694965a61244d91d1...HEAD: passed.
  • Two independent exact-diff reviews found no actionable blocker after the missing-DB, strict-manifest, and SQLite WAL race fixes.
  • Two additional independent reviews of the review-response diff found no actionable findings.
  • Full-suite green is not claimed. A broader Windows history run on a patch-equivalent head exposed three pre-existing/environmental failures that reproduce individually outside the added assertions: a held-lock helper exits 1, an existing whitespace-validator case exceeds its 5-second limit, and a POSIX chmod expectation observes Windows mode semantics.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed. No user-facing option or configuration contract changed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults. The review covered path binding, malformed/foreign manifests, Worker proof validation, WAL races, and fail-closed behavior.

Summary by CodeRabbit

  • Bug Fixes

    • Improved history migration reliability when no changes are pending.
    • Added verified no-op detection to prevent unnecessary migrations and backups.
    • Migration now continues safely when preliminary count checks are unavailable or inconclusive.
    • Added validation to ensure no-op results reference the correct database and backup state.
    • Preserved verified migration counts and backup details in completed history records.
  • Tests

    • Expanded coverage for valid, invalid, missing, and changing migration states.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

History migration now performs read-only no-op verification for canonical database and backup state. The worker returns proof-bearing converged results. Job validation checks proof metadata. The guardian relies on worker verification, and transitions persist verified counts.

Changes

History no-op migration

Layer / File(s) Summary
No-op snapshot verification
src/codex/history-provider.ts, tests/codex-history-provider.test.ts
The provider validates database state, backup manifests, file identity, and snapshot stability. Tests cover unknown, pending, and race outcomes.
Worker proof propagation and validation
src/codex/history-job.ts, src/codex/history-worker.ts, tests/codex-history-worker-boundary.test.ts, tests/codex-history-worker.test.ts
The worker emits verified no-op proofs. Job validation requires matching canonical paths and zero-change counts.
Guardian stopping and transition accounting
src/codex/history-migration-guardian.ts, src/codex/history-transition.ts, tests/history-migration-guardian.test.ts, tests/codex-transition-state.test.ts
Count probes are advisory. The guardian stops after mutations or verified no-op results. Transition state stores proof-derived counts.

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

Sequence Diagram(s)

sequenceDiagram
  participant Guardian
  participant HistoryWorker
  participant HistoryProvider
  participant HistoryTransition
  Guardian->>HistoryWorker: attempt migration
  HistoryWorker->>HistoryProvider: snapshot canonical history state
  HistoryProvider-->>HistoryWorker: verified no-op proof or pending state
  HistoryWorker-->>Guardian: converged result with proof
  Guardian->>HistoryTransition: persist migration outcome
Loading

Possibly related PRs

  • lidge-jun/opencodex#1106: Both PRs modify the Codex history migration pipeline, including history-job.ts, history-provider.ts, and history-worker.ts.

Suggested labels: review-ready

Suggested reviewers: lidge-jun, yuxin-qiao

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation moves no-op authority under the worker lock and validates database, manifest, path, fingerprint, and count proof requirements from issue #1183.
Out of Scope Changes check ✅ Passed The production and test changes directly support atomic no-op verification, migration fallback, proof propagation, and accounting requirements.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making Codex history no-op detection atomic.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added the bug Something isn't working label Aug 8, 2026
@luvs01

luvs01 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/codex/history-migration-guardian.ts (1)

88-104: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

A converged-but-unproven result now burns the whole tick budget and ends with a false "DB stayed locked" warning.

Line 91 makes moved > 0 || verifiedNoop the only stop condition. The previous recount-based stop was removed. Consider a database that is already fully on openai but whose backup manifest the provider cannot certify — for example a manifest written before the stateDbPath field existed. inspectBackupForNoop in src/codex/history-provider.ts line 272 requires typeof manifest.stateDbPath === "string" and otherwise returns manifest-schema, so snapshotCodexHistoryNoop returns unknown, and src/codex/history-worker.ts line 144 falls through to writeHistoryProviderTransition.

The resulting sequence on every tick:

  1. migrateFn() returns { rows: 0, files: 0 } with failed unset.
  2. moved is 0 and verifiedNoop is false, so line 91 does not stop.
  3. Line 104 reschedules.

This repeats for maxTicks ticks. Each tick acquires H and runs a full provider transition write, so the guardian performs repeated writes against a database that needs no work. At exhaustion, line 101 logs "Codex history DB stayed locked; legacy threads not yet migrated" and directs the user to close the Codex app and run ocx sync. No lock contention occurred and no legacy threads exist. The message is wrong and the recommended action is useless.

Separate the two exit conditions. Track whether any attempt was actually blocked or failed, and only emit the lock warning in that case. Better still, propagate the snapshot reason so the guardian can stop on a stable non-race unknown.

🩹 Minimal fix: do not claim a lock that never happened
   let stopped = false;
   let pending: { cancel(): void } | undefined;
   let ticks = 0;
+  let sawObstruction = false;
 
   const tick = async () => {
     ...
       const result = await migrateFn();
+      if (result.failed) sawObstruction = true;
       if (!result.failed) {
         ...
       }
     } catch {
+      sawObstruction = true;
       /* hard errors are not retryable state — fall through to the tick budget */
     }
     if (ticks >= maxTicks) {
       stopped = true;
-      log.log("⚠️ history-migration: Codex history DB stayed locked; legacy threads not yet migrated. Close the Codex app and run 'ocx sync' (or check 'ocx doctor').");
+      if (sawObstruction) {
+        log.log("⚠️ history-migration: Codex history DB stayed locked; legacy threads not yet migrated. Close the Codex app and run 'ocx sync' (or check 'ocx doctor').");
+      }
       return;
     }

The existing test at tests/history-migration-guardian.test.ts lines 125-144 already models the unverified zero-row path, so extend it with a run that never returns verifiedNoop: true and assert the warning is absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/history-migration-guardian.ts` around lines 88 - 104, Update the
guardian loop around migrateFn and the maxTicks handling to distinguish genuine
lock/attempt failures from a stable zero-work result that lacks verifiedNoop.
Stop cleanly for converged unverified results, or propagate the snapshot reason
as needed, and emit the “DB stayed locked” warning only when an attempt was
actually blocked or failed. Extend the existing history-migration guardian test
to cover repeated unverified zero-row results and assert that no lock warning is
logged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/codex/history-job.ts`:
- Around line 112-134: Update the worker-result validators around
isPlausibleWorkerResult and isPlausibleWorkerResultForTests to require
target.operation and accept proof objects only when it equals "migrate-openai";
preserve proofless done-result validation for other operations. Propagate the
full request operation from the existing caller, widen both target types
accordingly, and add a boundary test rejecting proofs on non-migrate-openai
requests.

In `@src/codex/history-migration-guardian.ts`:
- Around line 76-81: Remove the unused countFn probe and its dependency from the
history migration guardian, then update tests/history-migration-guardian.test.ts
to stop supplying countFn and asserting its invocations. Replace the stale
“Locked probe or pending work” comment with wording that reflects the
unconditional migration-pass attempt.

In `@src/codex/history-transition.ts`:
- Around line 36-40: Update the comment above pendingRows and backupEntries to
reflect that proof counts are verified worker results, including valid zero
values, while null is used only when no proof is available. Keep the existing
nullish-coalescing assignments unchanged and explicitly avoid describing zero as
manufactured or implying this path skips the relevant verification.

In `@tests/codex-history-provider.test.ts`:
- Around line 366-410: Ensure backup artifacts are cleaned up in both affected
test locations: in tests/codex-history-provider.test.ts lines 366-410, wrap each
test body in try/finally and remove backupPath and dir with forced cleanup,
matching the existing WAL-test pattern; in tests/codex-history-worker.test.ts
line 67, register historyBackupPathFor(stateDb) for teardown alongside the
sandbox root and verify getConfigDir() is consistent in the test process and
child processes using fixture.env.

---

Outside diff comments:
In `@src/codex/history-migration-guardian.ts`:
- Around line 88-104: Update the guardian loop around migrateFn and the maxTicks
handling to distinguish genuine lock/attempt failures from a stable zero-work
result that lacks verifiedNoop. Stop cleanly for converged unverified results,
or propagate the snapshot reason as needed, and emit the “DB stayed locked”
warning only when an attempt was actually blocked or failed. Extend the existing
history-migration guardian test to cover repeated unverified zero-row results
and assert that no lock warning is logged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 78aa40d4-1dbd-4b71-9216-52ae7e0892b3

📥 Commits

Reviewing files that changed from the base of the PR and between 14e9485 and a2a7ccf.

📒 Files selected for processing (10)
  • src/codex/history-job.ts
  • src/codex/history-migration-guardian.ts
  • src/codex/history-provider.ts
  • src/codex/history-transition.ts
  • src/codex/history-worker.ts
  • tests/codex-history-provider.test.ts
  • tests/codex-history-worker-boundary.test.ts
  • tests/codex-history-worker.test.ts
  • tests/codex-transition-state.test.ts
  • tests/history-migration-guardian.test.ts

Comment thread src/codex/history-job.ts Outdated
Comment thread src/codex/history-migration-guardian.ts Outdated
Comment thread src/codex/history-transition.ts Outdated
Comment thread tests/codex-history-provider.test.ts

luvs01 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@lidge-jun The review fixes are complete on exact head 402a2601ac7dd3376a7ab9fde1e14db82a1d34f8; all four review threads are resolved.

Author-side verification: Bun 1.3.14 related set 48/48 (210 assertions), transition mapping 2/2 (7 assertions), typecheck, privacy scan, and diff check passed.

The remaining gates require maintainer approval to run:

I am keeping the PR in draft until exact-head CI can run and report.

@lidge-jun

Copy link
Copy Markdown
Owner

Approved the pending Cross-platform CI run at this exact head, and it came back green.

Worth explaining the silence beforehand: contributor PRs queue their workflow runs as action_required until a maintainer releases them, and gh pr checks does not show that state — so from your side it looks like nothing is happening rather than like something is waiting on us. That was ours to clear, not yours.

To be precise about what this does and does not do: it only unblocks CI. It does not make this mergeable. The four-box readiness checklist in the description is your attestation, and the gate keeps the PR in draft until you complete it. With CI now green at your head, box 1 is provable.

If anything in the run looks wrong to you, say so and I will dig into it rather than leaving you to guess.

@luvs01
luvs01 force-pushed the agent/fix-history-migration-atomic-noop branch from 402a260 to c4e9919 Compare August 9, 2026 00:56

luvs01 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@lidge-jun #1294 has been rebased and revalidated on the current policy baseline. The new exact head is c4e9919d3a7415e535f26a46a87a601387e3c3a7 (two disjoint commits behind live dev, within the readiness bound).

Local exact-head verification passed: Bun 1.3.14 proof 1/1, related set 48/48 (210 assertions), transition mapping 2/2 (7 assertions), typecheck, privacy scan, and diff check.

The new fork workflow runs are awaiting approval:

I will complete the four-box readiness section after exact-head CI and automated review finish.

@luvs01
luvs01 force-pushed the agent/fix-history-migration-atomic-noop branch 2 times, most recently from 0236a60 to deaf978 Compare August 9, 2026 02:02
@luvs01

luvs01 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@lidge-jun Updated #1294 to exact head cbd53046d0617ee4fed03c52d3bb56a5de3eb261, rebased and revalidated on dev@8e6d661b2340352219d5e3d694965a61244d91d1. Live dev is five disjoint commits ahead, within the readiness bound.

Exact-head Bun 1.3.14 verification is green: proof 1/1, related set 48/48 (210 assertions), transition 2/2 (7 assertions), typecheck, privacy, and diff check. CodeRabbit and target checks are green; unresolved review threads remain zero.

Current exact-head workflows awaiting approval:

I will check the final two readiness boxes once both runs are green.

@luvs01
luvs01 force-pushed the agent/fix-history-migration-atomic-noop branch from deaf978 to cbd5304 Compare August 9, 2026 05:09
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (2/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 2/4).

Review readiness checklist

  • ⬜ All CI tests are green on my local testing.
  • ✅ I pushed my PR to the latest dev commit.
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

2/4 boxes ticked.

This PR stays in draft until every box above is ticked.

@lidge-jun

Copy link
Copy Markdown
Owner

Landed on dev as 6babcfa and 9b3d64d (both commits, your authorship preserved), rebased onto the current head.

Verified before merge: src/codex/history-migration-guardian.ts really did call countFn() and decide no-op before runCodexHistoryJob acquired serialization lock H, so a concurrent apply/restore could race it. Moving the no-op authority inside withHistoryWriteSerialization after desired-state revalidation is the right fix, and gating proof emission behind manifest/path checks, database identity, and a same-connection PRAGMA data_version fence makes it fail-closed rather than optimistic.

An independent reviewer went looking specifically for a missed correctness regression in the concurrent-migration design and did not find one. Full suite green on the rebased head (10199 pass, 0 fail).

Thanks — 500 lines is a lot for a no-op check, but the WAL, malformed-manifest, forged-proof, and guardian-retry cases each earn their place.

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.

2 participants