fix(gjc-runtime): fence continuation dispatch and repair the memory-guard split marker - #3604
fix(gjc-runtime): fence continuation dispatch and repair the memory-guard split marker#3604twoimo wants to merge 10 commits into
Conversation
…-independent Restacks the Windows psmux authority work onto current dev, dropping the storage lineage dev already absorbed via squash, and repairs every darwin-only failure in the slice. Five separate platform sources decided psmux behavior independently; four ignored the authority test seam entirely, so Windows-authority paths were unreachable on POSIX and eleven tmux-sessions tests failed there: - psmuxAuthorityEnvironments gated persisted-authority discovery on raw process.platform, aborting before any later guard could run. - resolveGjcTmuxProviderContext and createGjcTmuxSession each recomputed the platform instead of consulting the seam. - teamProviderAuthority read process.platform directly; GjcTeamConfig now carries the platform so startGjcTeam's value reaches it. Continuation dispatch had two real defects: the ack-loop deadline used raw Date.now() while every other timestamp in the file routes through the seamed clock, and the reservation site threw where the revalidation site records a skipped outcome, so one worker's short lease aborted an entire monitor pass. tmux-sessions: 35/35 on darwin (was 24/35). team-runtime: 115/117, with zero psmux-only failures - both remaining names also fail at dev, and this slice fixes three that dev fails.
psmux rewrote the darwin lifecycle spawn/cleanup path (completeNonLinuxLifecycleSpawn, cleanupDirectLifecycleAttempt), which regressed POSIX: dev is 60 pass / 0 fail on notifications-lifecycle-control-runtime, psmux was 51 / 9. psmux is a Windows-only provider and POSIX uses tmux, so the darwin path must stay dev's. Reverting the file to dev keeps every psmux suite green (tmux-sessions 35/0, psmux-detect 39/0, launch-tmux 137/0, tmux-gc 9/0) and preserves the win32 lifecycle content, which dev already carries from the earlier merged psmux-authority slices.
…the managed sync lock
…uation revalidation
…g continuation revalidation" This reverts commit ebc3ca196801cd6b50b726c51d22ed3ba704e9ce.
…ng continuation revalidation" This reverts commit 56f68822097d04d335126c7542f19e9dd720748e.
… the managed sync lock The Windows psmux authority publish previously acquired `acquireManagedLockSync` from managed-session storage. Dev removed that symbol when it reverted Yeachan-Heo#3489, so the import broke on merge and the whole slice failed at module load. Reviewers asked for the sync lock to return only alongside a real consumer and a shared async/sync release contract, which belongs to the resident storage branch rather than here. So this slice now enforces the property it actually needs on its own: a retained lock descriptor for the exact authority name blocks publication unless its owner process is definitely gone. Expiry alone is not permission, because a paused writer with an expired lease is still live and stealing the name would let two writers publish one generation-scoped authority. `publishManagedFileNoReplaceSync` already supplies create-without-clobber exclusion for the payload itself, so no lease helper is required.
…arker Repairs the two blockers the reviewer identified on the previous head. 1. A public authority-changing operation dispatched no continuation argv. The continuation reservation is written under the task lock, that lock is released, and only then does dispatch revalidate eligibility. A concurrent claim-releasing operation could land in that window, so revalidation observed no current claim and the incident was journalled skipped with nothing dispatched. Revalidation plus send-keys now runs inside the same team mutation fence every other public authority-changing operation takes, and the fence is released before the ACK wait because the ACK is published by a separate receiver process that must acquire the same cross-process fence. Worker GC prune now takes that fence too, since it deletes claim records. 2. Replacement settled before pane-launch ACK. The fake tmux rewrite dropped the three-field tmux-last-split marker its shell predecessor published on every split. The memory-guard replacement test waits on that marker before publishing the generation-bound startup ACK, so the ACK never arrived, relaunch hit its startup deadline, and the guard reported retrying. The marker is restored. The same test also aborted its marker waiter before its own advisory-host early return, which masked the advisory outcome on non-Linux hosts; the ordering is fixed.
Yeachan-Heo
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES — terminal hostile review of exact head 890bd21fdfc348aa1d9b745bb3865126af824130 against exact current-dev base dd26a0787260b4c54b6298b2f46b4908b2b1ed1a.
The two predecessor #3595 blockers are materially repaired in this successor. Exact-head CI proves team-runtime.test.ts green (job 90979894483), and source review confirms continuation revalidation + send-keys are held under withGjcTeamMutationFence, the fence is released before the receiver ACK wait, pruneTeamWorkerGcRecord takes the same fence, the receiver ACK re-enters through task mutation, and the fake tmux split marker is restored. I found no dependency on the reverted synchronous managed lock and no exact-file overlap with the sole #3596 branch.
P1 — the repaired exact head does not typecheck. Exact-head run 30574170803, required job 90979894452, fails packages/coding-agent check at src/gjc-runtime/team-runtime.ts:4534: readonly (string | undefined)[] is passed to a readonly string[] dispatch seam. The new frozen send-keys argv embeds worker.pane_id, whose type remains optional even after runtime eligibility validation. CI log SHA-256: 10b95e99ff89e21b5890ec6e6401d1bbe4e12baf60a6764c5a8c439c53e80a99.
Bounded repair: capture/narrow a non-empty pane id inside the fenced revalidation result before building argv (or return the typed skipped outcome), then use that proven string for both send operations. Keep the existing fence release before ACK and rerun a fresh exact-head package check plus team-runtime shard.
Exact-head CI is terminal with no pending checks: the team-runtime shard passed, but package check, evidence producer, and aggregate are red. Red CI is independently merge-blocking. Closing this external PR under doctrine; no external branch mutation, workflow rerun/cancellation, or duplicate internal mutation lane was created.
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
Thank you for the detailed review on #3595. Both P1 blockers you identified are repaired in this successor, and I want to be explicit about the causality I found for each, since in both cases the defect was slightly different from what the failure name suggested.
P1 (1) — a public authority-changing operation dispatched no continuation argv
You were right that this was a real production gap, not a flaky test. The mechanism:
continueStalledGjcTeamWorkerswrites the attempt reservation under the task-store lock, releases that lock, and only then revalidates continuation eligibility beforesend-keys. That leaves an unguarded window.pruneTeamWorkerGcRecord— a claim-releasing operation — could land inside it, delete the claim record, and the subsequent revalidation would correctly observe "no current claim" and journal the incidentskippedwith zero dispatches.The repair is the fence-hold restructure the previous PR body deferred:
send-keysnow runs insidewithGjcTeamMutationFence, the same fence every other public authority-changing operation already takes. Claim mutation and dispatch are now mutually exclusive.allows a spawned receiver CLI ACK while continuation waits outside the mutation fencepins that contract, and it stays green — I verified an earlier, coarser version of this fix that held the fence across the whole pass, and it broke exactly that test, which is what pointed me at the correct boundary.pruneTeamWorkerGcRecordnow also takes the fence, since it deletes claim records and rewrites claimed tasks.The fence closure returns a typed outcome (
skipped/dispatched/threw) so the post-fence code paths stay explicit rather than reading mutated outer state.P1 (2) — replacement settled before pane-launch ACK
This one turned out to be a test-fixture regression introduced by this PR's own rewrite of
createFakeTmuxBin, and I am glad you held the line on it, because the red was genuine.The predecessor shell-based fake wrote a 3-field marker to
tmux-last-spliton everysplit-window. The rewritten TypeScript fake updated onlycountPathand dropped that marker write entirely. The memory-guard replacement test waits on that exact marker to know a successor pane was launched before it publishes the generation-boundworker-startup-ack. With the marker never written, the ACK never arrived,relaunchWorkerPaneForMemoryGuardhit its startup deadline, and the guard reportedretrying— precisely the symptom in your CI log. Restoring the marker write fixes it.I also fixed a separate structural bug in that same test that made it unrunnable on non-Linux hosts: it aborted the split-marker waiter and then
awaited it before reaching its ownprocess.platform !== "linux"early return, so the rejected waiter masked the advisory outcome. The platform branch now cancels and swallows the waiter first.Verification
packages/coding-agent/test/gjc-runtime/team-runtime.test.tson this exact head, rebased onto currentdev:The three cases you flagged or that I repaired are individually green:
One honest caveat, stated plainly rather than buried: the single remaining local failure is
requires exactly one current claim and rejects shutdown, draining, lease, and pane authority gaps, which exceeds its explicit 20s cap on my macOS host. It is not in your CI failure set (your run showed 115/2 with only the two P1s), it timed out identically on my very first baseline run before I made any edits, and its scenario list is byte-identical betweendevand this head — so I believe it is host slowness from 13 sequential git-repo fixtures, not a logic regression. I did not touch it, and I would rather flag it than quietly claim a fully green shard.Scope note
This branch carries no dependency on the reverted #3489.
acquireManagedLockSyncandManagedStorageLockSyncdo not appear anywhere in it — the head commit explicitly enforces psmux authority migration exclusion without the managed sync lock, andpersistGjcTmuxProviderAuthoritySyncis defined within this branch's owntmux-provider-context.ts. I have also not opened any competing branch against the session-storage subsystem, which I understand is under #3596's sole ownership.Rebased cleanly onto
devatdd26a0787. Happy to keep iterating if any of the above reasoning does not hold up under your independent check.