drive: cloud run e1d7225d - #18
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesWatch Registration Race
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to This change is merge-ready after normal checks and review; no actionable merge-blocking risk remains. Sequence Diagram(s)sequenceDiagram
participant AppendThread
participant Journal
participant PausingObserver
participant watch_with_replay
AppendThread->>Journal: commit journal entry
Journal->>PausingObserver: notify committed entry
PausingObserver-->>AppendThread: pause notification
watch_with_replay->>watch_with_replay: acquire run lock
watch_with_replay->>watch_with_replay: register watcher and read snapshot
watch_with_replay->>watch_with_replay: replay entries and record watch_ready
PausingObserver-->>AppendThread: release notification
Journal->>watch_with_replay: deliver buffered live entry
Poem
Warning Some tools did not complete. Review the errors below. 🔧 Clippy (1.97.1)Clippy execution failed Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/settings/billing. Comment |
What this isA fix for the intermittent hang in The change takes the run lock so an append's journal commit and its hub notification are atomic with respect to registration, preventing a replayed entry from later arriving as live. The test seam is renamed VerifiedNot verified — please weigh this before mergingThe brief required a test that fails against current code and passes after the fix. I tried to confirm that by reverting Two readings, and I cannot separate them from here:
The reasoning behind the fix is sound and the whole suite is green, so this is not a change I think is wrong. But a race fix whose regression test has never been seen to fail is a fix on trust, and this program's standard is that a gate which cannot fail is not a gate. Worth a human deciding whether that is good enough here. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e00e11ecd
ℹ️ 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".
| let lock = hub.run_lock(run_id); | ||
| let _guard = lock.lock().expect("run lock"); |
There was a problem hiding this comment.
Release the run lock before writing replay frames
In the Unix-socket run.watch path, this guard remains held while every historical entry is written through the blocking UnixStream. If a journal is large enough to fill the socket buffer, or a subscribed client stops reading, write_frame can block indefinitely and prevent run.resume, step.complete, event.emit, stream.append, and reconciliation from acquiring the same per-run lock, halting the workflow. Keep the lock only through watcher registration and snapshot acquisition, then release it before sending the replay; subsequent appends can remain buffered until watch_ready.
Useful? React with 👍 / 👎.
| // Without the run lock, registration reaches Live while the committed | ||
| // append's hub notification is still paused. With the lock, this times out | ||
| // because registration correctly waits for that notification to finish. | ||
| let _ = watch_is_ready.recv_timeout(Duration::from_millis(100)); |
There was a problem hiding this comment.
Synchronize the race test instead of ignoring the timeout
If the watch thread is not scheduled within this 100 ms window, recv_timeout returns an error even when the production lock is removed; because that result is discarded, the test then releases the append, allows it to notify before watcher registration, and observes a valid single replay, so the regression can pass. Replace this scheduling timeout with an explicit barrier or other deterministic signal proving the watch attempt has reached the contested lock before releasing the paused observer.
AGENTS.md reference: AGENTS.md:L19-L21
Useful? React with 👍 / 👎.
…ation Refreshing ground truth BEFORE it goes stale again. A stale STATE.md has cost two runs already: one escalated on a contradiction it could not resolve, another was told two completed items were still missing. An assessor in a sandbox has no git history — this file is its history. Three PRs are open and named so no run duplicates their work. Gate 2's entry now records that a real external event HAS woken the flow, with exactly-once holding across repeated live polls, and that what remains is Khaliq's judgement on rule 2 rather than a missing part. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review was right. The first version of this fix held the run lock for the whole of watch_with_replay, including the loop that writes every historical entry through a blocking UnixStream. A slow or stalled reader would have pinned that lock for the duration, blocking every other operation on the run — a fix for a race that introduced a head-of-line block. The lock now covers registration and the snapshot read and nothing else. That is all it needs to cover: the guarantee required is that registration and the snapshot are atomic with respect to an append, so no entry can slip between them. Once both have happened the set is fixed, and live entries arriving during the writes are buffered by the hub and flushed deduped against replayed_through_seq. The error path drops the guard before unwatching rather than holding it across that call too. Verified: kernel 19+19+1+1+26+5+6 passed, 0 failed, including an_entry_appended_during_watch_registration_is_delivered_exactly_once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both findings addressed — and the P2 explains something I could not account for earlier. P1 — release the run lock before writing replay frames: fixedYou were right, and the shape of the mistake matters: a fix for a race that introduced a head-of-line block. The guard covered the whole of The lock now covers registration and the snapshot read only. That is all it needs to cover — the guarantee required is that those two are atomic with respect to an append, so nothing can slip between them. Once both have happened the set is fixed, and live entries arriving during the writes are buffered by the hub and flushed deduped against Verified: kernel 19+19+1+1+26+5+6 passed, 0 failed, including the target test. P2 — the 100ms
|
* gate 3: snapshot the backlog once, and prove the steps agree (PR #20 P2s) Two review refinements from PR #20, plus the test its DoD asked for. The flow re-read ops/BACKLOG.md in every step, so a backlog edit between select-entry and emit-package produced a package describing an entry that was never selected — a Garden reporting work it did not choose. read-backlog now snapshots the file once and the later steps read the snapshot and the selected entry, so the steps cannot disagree. Package metadata is derived from the selected entry rather than hardcoded. The test runs the flow's ACTUAL shell commands, not a reimplementation — a test of a paraphrase would pass while the flow stayed broken — and mutates the backlog mid-run to force the condition. Confirmed it FAILS against the unfixed flow before trusting it: × expected '{"title":"Swapped entry"...}' to contain 'Original entry' and passes against the fixed one. A regression test never seen to fail proves nothing; PR #18 is still carrying exactly that gap. Verified: sdk 159 passed across 12 files, tsc --noEmit clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: clear the selected entry before selecting, so no run inherits the last one (PR #21 P1) My own fix created this. Snapshotting the backlog stopped the two steps disagreeing within a run, but it did so with shared persistent state — and shared state leaks across runs. If select-entry finds nothing actionable it exits before writing, so emit-package read the PREVIOUS run's entry and presented it as this run's choice. A Garden confidently proposing yesterday's work as today's. select-entry now removes .relayflow/backlog-picker-entry.json before it attempts selection, so a failed selection leaves nothing behind to inherit. Confirmed the test FAILS without the fix before trusting it: × expected '{"title":"Yesterday entry"...}' not to contain 'Yesterday entry' Verified: sdk 160 passed across 12 files. The P2 (the files_in_scope regex matching backticked prose that contains a slash) is not addressed — real, cosmetic, and safe to take next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Relayflow Lead <lead@relayflows.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Recording straight after the merge, as with #20. Both of the picker's properties now have tests confirmed to fail without their fixes: the two steps cannot disagree within a run, and no run inherits the previous run's selection. Noting in the same breath that #18 still does not meet that standard — its regression test has never been observed to fail — so the difference is visible to whoever reads this next rather than buried in a PR comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The production fix is sound and reviewed — the lock is correctly scoped off the blocking replay writes. But its test rests on a 100ms recv_timeout, which is a scheduling race, so it can pass without the fix. I could not make it fail, and review explained why. Merged deliberately: an unfixed race in gate-1 code is worse than a correct fix with a weak guard. Recording plainly that this is the only change in this stretch that does not meet the fail-first standard the others did, with the specific rework needed — explicit synchronisation around the after_ready seam, confirmed failing against pre-fix code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Work produced by cloud run e1d7225d-2e0a-4919-be38-b9dde5727315 in a workflow sandbox and delivered from this host, because a sandbox has no remote and no GitHub token. Verification and adversarial review ran in-run; see ops/reviews/ in the diff.
Review was right. The first version of this fix held the run lock for the whole of watch_with_replay, including the loop that writes every historical entry through a blocking UnixStream. A slow or stalled reader would have pinned that lock for the duration, blocking every other operation on the run — a fix for a race that introduced a head-of-line block. The lock now covers registration and the snapshot read and nothing else. That is all it needs to cover: the guarantee required is that registration and the snapshot are atomic with respect to an append, so no entry can slip between them. Once both have happened the set is fixed, and live entries arriving during the writes are buffered by the hub and flushed deduped against replayed_through_seq. The error path drops the guard before unwatching rather than holding it across that call too. Verified: kernel 19+19+1+1+26+5+6 passed, 0 failed, including an_entry_appended_during_watch_registration_is_delivered_exactly_once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
abcb4ef to
05cb7d9
Compare
…23 The brief still asked for the picker/consumer join, which PR #23 merged. Left alone, autodrive would have spent every cycle rebuilding finished work — the same waste as the false brief earlier, from the opposite direction. It now points at what the loop genuinely cannot do: survive a malformed backlog entry, or notice that a package scopes files which do not exist. Both are real gaps with testable definitions of done. STATE.md records #18 and #23, and keeps gate 1's asterisk visible: its race regression test has never been observed to fail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Applying the ordering rule adopted after #46: retarget first, then merge the PR that completes the previous task, so no run launches against work that is one merge from done. Next target is gate 1's only asterisk. PR #18's race fix is sound but its regression test rests on a 100ms recv_timeout and has never been observed to fail. The after_ready seam at server.rs:427 already exists to pin the ordering properly; the work is the test and the proof, not production code.
Both review findings were about NEXT.md rather than the test: it did not carry the fail/pass mutation transcript, and a documented command assumed the wrong working directory. NEXT.md is per-run scratch and this PR has no reason to modify it, so it now matches main and carries neither problem. The transcript the review asked for is real and is recorded on the PR: the test was run against a locally reverted PR #18 and FAILED, then against the restored fix and passed, then 20 consecutive times without a flake.
* drive: cloud run a983d1de Work produced by cloud run a983d1de-d9d4-46a8-80f5-8018a5efa771 in a workflow sandbox and delivered from this host, because a sandbox has no remote and no GitHub token. Verification and adversarial review ran in-run; see ops/reviews/ in the diff. * chore: keep ops/NEXT.md out of this PR Both review findings were about NEXT.md rather than the test: it did not carry the fail/pass mutation transcript, and a documented command assumed the wrong working directory. NEXT.md is per-run scratch and this PR has no reason to modify it, so it now matches main and carries neither problem. The transcript the review asked for is real and is recorded on the PR: the test was run against a locally reverted PR #18 and FAILED, then against the restored fix and passed, then 20 consecutive times without a flake. --------- Co-authored-by: Relayflow Lead <lead@relayflows.local>
Automated drive work from cloud run
e1d7225d-2e0a-4919-be38-b9dde5727315.The sandbox cannot open PRs (no remote, no GitHub token), so this was delivered
from a host that can. Verification and adversarial review ran in-run — see
ops/reviews/in the diff. A human merges.