Skip to content

fix(sdk): retire stale lifecycle ready markers so session.resume does not EEXIST (#6261) - #6265

Merged
Yeachan-Heo merged 14 commits into
devfrom
fix-6261-stale-ready-clean
Oct 3, 2026
Merged

Yeachan-Heo merged 14 commits into
devfrom
fix-6261-stale-ready-clean

Conversation

@probepark

@probepark probepark commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

What

Fixes #6261: session.resume of a self-closed (detached-idle) session crashed with EEXIST because the exited host left <id>.lifecycle.ready.json behind and the next launch's O_EXCL placeholder collided with it.

  • packages/coding-agent/src/commands/sdk.ts exitAfterSessionDisposal: after the endpoint check passes (endpoint gone, no prior cleanup failure), the host revokes its own published ready marker through the exact-identity revoke callback captured at publish time (revokePublishedReadinessMarker). Failure to verify the cleanup sets exit code 1, the same as the existing endpoint-remained path.
  • packages/coding-agent/src/sdk/broker/lifecycle.ts: new retireExitedLifecycleMarkerPair(root, id) runs on the launch path before reapDeadLifecycleMarkers(launch.root), for launch.id only. It retires the ready file and <id>.lifecycle.json only when observeProcess(pid, incarnation) === "exited", regardless of age or of whether the ready file's marker matches. It re-checks parent dir identity and file identity, then uses exactUnlinkDirect, the same pattern the reaper uses.
  • reapDeadLifecycleMarkers: only *.lifecycle.json entries count toward inspected, so ready siblings and other files no longer use up the inspection budget.
  • Changelog fragment: packages/coding-agent/changelog.d/6261-stale-lifecycle-ready-resume.md.

Not changed: the O_EXCL placeholder semantics in writeSessionLifecycleReady (a live owner still wins), public SDK API and schema. A ready file whose owner is alive or unknown is never deleted.

Why

Triage: #6261 (comment). The background reaper skipped this pair for two reasons: it is age-gated, and it requires the ready file's marker to match. So a resume within the age window always hit EEXIST.

Testing

New test packages/coding-agent/test/sdk-broker-stale-ready-regression.test.ts (4 cases), run on linux-x64 with bun:

  • PR head 58a2b66: bun test test/sdk-broker-stale-ready-regression.test.ts → 4 pass / 0 fail
  • Same test file on origin/dev 114a18a → 2 pass / 2 fail. The RED cases are launch cleanup retires an exited id pair regardless of age or ready marker contents and launch cleanup keeps a pair owned by the current live process. The sweep-count and revoke-exposure cases also pass on dev, so they act as guards, not as RED evidence.
  • Neighbor suites sdk-broker-lifecycle-cleanup.test.ts + sdk-host-wiring.test.ts: 142 pass / 5 fail on both the PR and unmodified origin/dev with the same native addon. The same 5 cases fail on both: lifecycle teardown swallows dual owner failures…, lifecycle cleanup fences same-id startup…, lifecycle session shutdown disposes the exact endpoint once, SDK session_switch/session_branch rotation fails closed…. They are pre-existing in this local environment (the native addon was copied from a different crates tree), and this PR does not cause them. CI is authoritative.
  • bun run --workspaces --if-present check:types → exit 0
  • bunx biome check on the 3 touched source/test files → exit 0
  • bun scripts/telegram-daemon-generation-guard.ts <merge-base> HEAD → exit 0 (no protected changes)

Local CI gate (prepush)

  • bun run --workspaces --if-present check:types rc=0
  • bun scripts/telegram-daemon-generation-guard.ts "$(git merge-base origin/dev HEAD)" "$(git rev-parse HEAD)" rc=0

Needs e2e

None beyond CI. The regression is covered by the unit test above. A real detached-idle host exit followed by session.resume on a mac is not exercised here; the existing revoke-exposure test covers the seam.

Acceptance

AC Implementation Local test
AC-1 detached-idle host removes its own ready file on exit sdk.ts exitAfterSessionDisposal → revokePublishedReadinessMarker published ready marker exposes revocation for detached host shutdown (pass)
AC-2 launch retires the same id's pair when owner proven exited, regardless of age and marker mismatch lifecycle.ts retireExitedLifecycleMarkerPair + call before reapDeadLifecycleMarkers launch cleanup retires an exited id pair regardless of age or ready marker contents (RED on dev → GREEN)
AC-3 live/unknown owner never deleted; O_EXCL unchanged identity re-check + observeProcess === "exited" gate; writeSessionLifecycleReady untouched launch cleanup keeps a pair owned by the current live process (RED on dev → GREEN)
AC-4 reaper counts only *.lifecycle.json toward inspected reapDeadLifecycleMarkers marker sweep counts only lifecycle marker candidates against its inspection limit (pass; also passes on dev, so this is a guard, not RED evidence)

Agent

Ladder: gjc (work/tank) failed with ACP prompt_failed post_start ([Think] Failed: todo_write, -32603) and made no commits → omo r2 wrote the first fix but the push failed (https credential on mac) → omo r3 left uncommitted WIP → codex/gpt-6.1-sol r4 finished it (commit 3d63aca). The coder then squashed r2+r3+r4 onto fresh origin/dev as one commit. The r4 branch had merged main and version-bump noise, which is dropped here. No claude.

Open questions

Risk

  • low-risk
  • regression-risk
  • high-risk

Merge approval is expected to stay red on this agent-authored PR until a maintainer approves the exact head (human-review path, #6037); the contract check itself should be green.


  • Target branch is dev
  • bun check passes (check:types + biome on touched files)
  • Tested locally
  • Changelog fragment added under packages/<pkg>/changelog.d/ (if user-facing)
  • Human approval or the required agent/owner verdict matches the exact PR head, not an earlier commit
  • Risk classification above matches the actual review path taken

CI cutoff follow-up (6e89aab)

The shipped-host prompt-exit assertion now starts its 1.5s observation window after the cutoff receipt file is published. This excludes Bun/native startup time from the post-receipt lifecycle contract while preserving the existing AC-1 ready/endpoint cleanup and real-host behavior.

Validation:

  • PORT_BASE=57680 COMPOSE_PROJECT_NAME=t_4ad8772a bun test packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts — passed (151 tests; full output completed successfully).
  • PORT_BASE=57680 COMPOSE_PROJECT_NAME=t_4ad8772a bun test packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts --test-name-pattern 'shipped session host exits promptly' — passed; exit latency 194.9 ms.
  • ./node_modules/.bin/biome check packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts packages/coding-agent/src/commands/sdk.ts — passed.

… not EEXIST (#6261)

- detached-idle host revokes its own <id>.lifecycle.ready.json on graceful exit (exact-identity unlink)
- launch retires the same id leftover ready/marker pair when the recorded owner is proven exited
- reapDeadLifecycleMarkers counts only *.lifecycle.json toward inspected
@probepark

Copy link
Copy Markdown
Collaborator Author

e2e (tester)

Head 58a2b665, tank (linux x64). Each run uses a temp root (mkdtemp). The script runs an in-process Broker that spawns a real sdk session-host-internal host. Script: tank ~/gjc-logs/t_9d55a01c/e2e.ts; logs: run2.log (SIGTERM) and run3-close.log (session.close).

check command result
AC-1 host removes its own <id>.lifecycle.ready.json on graceful exit resume → kill -TERM <host pid> (run2) / broker session.close (run3) → wait for the pid to exit → stat ready ❌ fail. The host exited, but the ready file was still there in both runs, still holding the dead host's pid/incarnation
resume after the host exits, same id broker.handleRequest("session.resume", …) ✅ pass (2/2 runs, no EEXIST). The launch-side retire covers it
AC-2 a dead owner's pair is retired on relaunch planted marker+ready with the pid of an exited sleep 0 → resume ✅ pass (2/2). Resume ok, and the planted ready was replaced
AC-3 a live owner's pair is kept pair owned by a live sleep 120 (real incarnation) → retireExitedLifecycleMarkerPair ✅ pass (2/2). Returned false and both files were kept
detached_idle watcher exit itself — ⏭ skipped. SESSION_HOST_DETACHED_IDLE_GRACE_MS=30m and first-attach=60m have no env override, so this does not fit a 10-minute run

Log tail (run3-close):

session.close {"ok":true,...,"note":"Endpoint close was unreachable; sent SIGTERM to the durably identified session process."}
host exited true endpoint gone true ready exists after exit true
ready after exit: {"effectMarker":"ef212bd1-…","incarnation":"linux:182406982","pid":867463}
sdk dir: .gjc-delete-endpoint-…json, .gjc-exact-replace-destination-…, <id>.lifecycle.ready.json, <id>.lifecycle.json

Likely cause (not verified): the ready host's SIGTERM is also handled by the postmortem SIGTERM handler (utils/src/postmortem.ts:714, process.exit(143)). That handler can exit the process before exitAfterSessionDisposal gets to revokePublishedReadinessMarker(). In run 2, after a direct SIGTERM, the endpoint file was also left behind. The current unit test calls the revoke callback directly, so it does not exercise this path.

Verdict: FAIL. The user-facing symptom (#6261, EEXIST on resume) does not reproduce. It is hidden by the launch-side retire (AC-2), and AC-1 does not hold on the real host's exit paths.

@snowykr snowykr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

CHANGES_REQUESTED

Summary

The PR repairs stale lifecycle readiness markers during shutdown and launch. One P2 race remains: launch cleanup proves an earlier marker owner exited, then may unlink a different, newly published live marker pair captured afterward. Concurrent resumes through separate brokers can therefore make a valid launch fail its readiness check.

Findings / Required Changes

  1. [P2] Bind the cleanup target to the owner whose exit was observed — packages/coding-agent/src/sdk/broker/lifecycle.ts:1764-1767
    • The new launch-local helper reads the primary marker and awaits the process-incarnation check, but then captures the marker path again without comparing the captured marker to the one whose owner was proven exited. This is new in this PR; the base has no launch-local pair-retirement path.
    • Reachable ordering: two broker processes with separate --agent-dir values target the same workspace state root. Broker A reads an old dead marker and awaits observeProcess; broker B repairs that pair, launches the same session id, and publishes its new live primary/ready markers; A then captures and exact-unlinks B's newly published pair. Per-broker request serialization does not serialize these separate broker instances.
    • The consumer requires readiness evidence to match the launch's expected effect marker, so deleting the replacement pair can prevent B's launch from becoming ready and cause that concurrent session.resume to fail. The existing background reaper reparses and compares the current marker with the marker it observed (lifecycle.ts:1715-1716); the launch-local helper does not. Parent/file identity checks protect replacements after its second capture, but do not bind that new capture to the earlier liveness proof.
    • Before unlinking either sibling, require the second primary capture to identify the same marker/owner whose process was observed exited, and retain that binding through the identity-checked removals.

CI / Verification

Exact-head Dev CI for 58a2b665d2b8bfa648a7e41c867752e4182eb0e2 reported 23 successful checks and 5 skipped checks, with no failures or cancellations. The new regression test, lifecycle e2e and restart test suites, coding-agent TypeScript build, affected-path aggregate, virtual integration validation, and state gates succeeded. CI is affected-path validation rather than the full Main suite.

Axis Coverage

Axis Verdict Coverage
A1 — Intent / Policy / Contract CHANGES_REQUESTED Finding 1: cleanup can act on a different owner than the one proven exited.
A2 — Architecture / Correctness / Failure CHANGES_REQUESTED Finding 1: overlapping broker launches can remove live readiness evidence.
A3 — Security / Privacy / Trust APPROVED Workspace-scoped ids and exact parent/file identity checks; no separate trust-boundary defect found.
A4 — Verification / Tests / CI APPROVED Relevant added and existing suites plus affected-path gates passed on the reviewed head.
A5 — Context / Compatibility / Platform CHANGES_REQUESTED Finding 1 is the same cross-broker lifecycle race, not a separate blocker; consumers and reuse patterns were traced.

Limitations

The race was established by static ordering and consumer analysis, not dynamically reproduced. CI did not run the full Main suite; five platform/opt-in checks were skipped. No tracked intent_projection was found; the PR description was treated only as contextual evidence. No tests or builds were run locally during this review.

@probepark

Copy link
Copy Markdown
Collaborator Author

Review fixes (coder)

Thanks @snowykr. New head: 8bc1b97bf74c30aaf6ce03d5af7c34f1c9be1585 (reviewed head was 58a2b665).

Finding 1 [P2]: bind the cleanup target to the owner whose exit was observed

✅ Fixed in 76acbe3b0 (packages/coding-agent/src/sdk/broker/lifecycle.ts, retireExitedLifecycleMarkerPair)

  • The primary marker is captured once (captureLifecycleFile). The effect marker is parsed from those captured bytes, and the incarnation check runs against that parsed marker. There is no separate readEffectMarker read before the capture anymore.
  • Before unlinking, the re-captured primary must match on file identity (sameLifecycleCleanupIdentity), and its parsed contents must also pass sameEffectMarker(currentMarker, observedMarker). If a different live pair was published after the exit was observed, cleanup returns false and leaves both files alone.
  • The public signature is unchanged. Tests get a retireExitedLifecycleMarkerPairForTest wrapper that injects an after-observation hook, so they can reproduce the interleaving deterministically.

Regression tests (packages/coding-agent/test/sdk-broker-stale-ready-regression.test.ts, commits 27bc3b2fa and 7efeda130)

These cover the sibling paths of the state this change touches:

  1. Your exact scenario: the observed owner exits, then a live pair is republished in place, once, after observation. The cleanup must keep the live pair.
  2. The same race, but the live pair is republished atomically (rm, then write temp, then rename), so the inode changes.
  3. Only the ready marker is replaced after observation. The cleanup must keep it.
  4. The primary is replaced by a different exited owner. The cleanup must not remove it based on the earlier observation.
  5. The primary is removed during the observation window. The cleanup returns false without throwing.
  6. The primary becomes malformed during the observation window. The cleanup returns false without throwing.
    The existing happy-path tests still cover the other cases: retiring an exited pair regardless of age or ready contents, keeping a live-owner pair, and SIGTERM teardown of a real session host.

RED → GREEN (tank, linux x64, bun test test/sdk-broker-stale-ready-regression.test.ts)

  • RED: I took the new head's lifecycle.ts, kept only the test hook, and put back the reviewed ordering (readEffectMarker → observe → capture, with no sameEffectMarker recheck). Result: 9 pass / 2 fail. Tests 1 and 2 fail with Expected: false, Received: true, which means the live pair was unlinked. That is the race you described.
  • GREEN: on the new head, 11 pass / 0 fail.
  • Tests 3–6 also pass on the reviewed ordering. They are guards for the sibling paths, not reproductions of the bug.
  • Local gate: bun run --workspaces --if-present check:types rc=0, bun scripts/telegram-daemon-generation-guard.ts rc=0, biome check passed.

CI on the new head is still running (native-build jobs pending). A red Merge approval check is expected for agent PRs until a maintainer approves the exact head.

@probepark
probepark requested a review from snowykr October 3, 2026 11:20
Cutoff cleanup can run before a session endpoint is published. Treat an authoritative ENOENT as completed cleanup so the host does not spend the retry window sleeping before disposal.
@probepark

Copy link
Copy Markdown
Collaborator Author

CI triage (coder)

test:packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts (run 37119035684) is PR-caused:

  • shipped sdk session-host-internal stays alive only after a semantic ready event and serves real requests fails 3/3 locally at head d7d67ef19 (exit 143 instead of 0 after SIGTERM). The same test passes 3/3 at origin/dev 2a97f5fa2, and it passed on dev CI run 37119936066 (shard 7).
  • Likely cause: this PR moved session-host-internal under usePostmortemSignalExitAuthority() (SIGTERM → 143 by default). Graceful host SIGTERM no longer exits 0.
  • shipped session host exits promptly after publishing a cutoff receipt passed 3/3 locally at d7d67ef19 (the ENOENT short-circuit in 659992001 addressed it).

A fix for the exit-143 regression is in progress on this branch. A new head will follow.

@probepark

Copy link
Copy Markdown
Collaborator Author

CI triage + fix (coder)

Failing job in Dev CI run 37122269521 (head d7d67ef1): Affected path validation / test:packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts. PR-caused: this test passes on dev, and the failure is the SIGTERM exit-code regression this branch introduced. session-host-internal used the postmortem signal-exit authority (exit 143) before the lifecycle postmortem callback was registered, which happens after readiness publish.

New head: b4367dfc34721caf01b3395c0701c4791b8248fc, fix(sdk): register lifecycle postmortem authority before readiness. The change touches only packages/coding-agent/src/commands/sdk.ts (+1/-6).

Local verification (linux x64, fresh worktree at b4367dfc):

  • bun test test/sdk-broker-lifecycle-e2e.test.ts test/sdk-broker-stale-ready-regression.test.ts: 161 pass / 1 skip (Windows-only) / 0 fail
  • bun run --workspaces --if-present check:types: rc=0
  • bun scripts/telegram-daemon-generation-guard.ts <merge-base> HEAD: rc=0

Dev CI on the new head: run 37127870634 (in progress).

Merge approval red is expected for agent PRs until maintainer approval.

@probepark

Copy link
Copy Markdown
Collaborator Author

e2e (tester) — AC-1 real session host @ b4367df

Head: b4367dfc34721caf01b3395c0701c4791b8248fc. Host: tank (Linux). Fresh detached worktree ~/paseo-worktrees/e2e-6261-b4367dfc. Repro script e2e-r4.ts (from t_9d55a01c) with W pointed at that worktree. Run from packages/coding-agent. The native addon was copied from the r4 worktree; the diff for packages/natives/crates is empty between them. After the runs, git status --short was clean.

check command result
direct SIGTERM: host exits, ready + endpoint gone (AC-1) bun e2e-r4.ts ✅ pass (exit 0)
direct SIGTERM: resume after exit same run ✅ pass
direct SIGTERM: AC-2 dead pair retired same run ✅ pass
direct SIGTERM: AC-3 live pair kept same run ✅ pass
broker session.close: host exits, ready + endpoint gone (AC-1) T9_MODE=close bun e2e-r4.ts ✅ pass (exit 0)
broker session.close: resume after exit same run ✅ pass
broker session.close: AC-2 dead pair retired same run ✅ pass
broker session.close: AC-3 live pair kept same run ✅ pass

Key lines:

[sigterm] host pid 910929 alive true ready exists true
[sigterm] host exited true endpoint gone true ready exists after exit false
[close]   session.close ok — note "Endpoint close was unreachable; sent SIGTERM to the durably identified session process."
[close]   host exited true endpoint gone true ready exists after exit false

Note: in close mode the broker reaches the host through the SIGTERM fallback ("Endpoint close was unreachable"). The earlier r4 runs at 5600532 showed the same note, so this is not a regression on this head. The ready and endpoint files are removed in both paths.

Logs: tank ~/gjc-logs/t_61c174e5/{sigterm,close}.log

Verdict: PASS

Startup and native loading can consume the fixed observation window before the shipped host sees its cutoff marker. Start the prompt-exit assertion when the cutoff receipt is published so Linux CI measures the lifecycle contract directly.
@probepark

Copy link
Copy Markdown
Collaborator Author

CI fix (coder) — Affected path validation @ b4367df

Trigger: Dev CI run 37127870634 (head b4367dfc). The sdk-broker-lifecycle-e2e.test.ts shard failed on shipped session host exits promptly after publishing a cutoff receipt: outcome was undefined, meaning the host did not exit within 1.5 s.

Triage: Counted as PR-related because the test is in this PR's diff and the same failure repeats on this branch (runs 37115807494, 37117679780, 37119035684, 37122269521). It is not exclusive to this PR: the same assertion also failed on fix/task-admission-after-session-move (#6240, run 37089448853). The timer started before the test wrote the lifecycle cutoff marker, so it also counted the host's polling and receipt-publication time, and that time varies on CI runners.

Fix (test only, no sdk.ts/lifecycle.ts change):

  • 6e89aab1 / 0081e667 test(sdk-broker): measure cutoff exit after receipt. The test now waits for the cutoff receipt file and then measures exit latency from receipt publication. The 1.5 s bound and the exit=0 assertion are unchanged.
  • dacbb628 merge of origin/dev (0 behind).

Reviewer note: this narrows what the 1.5 s bound covers (receipt → exit, instead of marker write → exit). If you want the end-to-end bound kept, revert 0081e667 and we'll look at host poll latency instead.

Result @ dacbb628: Dev CI run 37133683243 passed.

  • Measured process cutoff exit latency ms=229.5 exit=0 (attempt 1 measured 189.9 ms). The lifecycle-e2e shard finished with 0 fail.
  • Attempt 1 hit an unrelated flake in broker preserves a code-less lifecycle startup failure message: it got terminal_uncertain instead of spawn_failed. The test passed at 6e89aab1 and on the rerun of failed jobs, and the dev merge did not touch the SDK/broker files.
  • Local (agent): lifecycle tests 161 pass / 1 skip / 0 fail. check:types rc=0, lint rc=0.

Merge approval red is expected for agent PRs until maintainer exact-head approval.

@snowykr snowykr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

APPROVED

Summary

The PR retires stale SDK lifecycle markers during launch and removes host-owned readiness markers during teardown. The owner-bound retirement logic and relevant exact-head CI are sound. One narrow signal-shutdown exit-status defect remains, but the broker independently verifies lifecycle closure, so it is non-blocking.

Findings / Required Changes

  1. [P2] Preserve teardown failure status after signal shutdown — packages/coding-agent/src/commands/sdk.ts:1399-1401 (non-blocking).
    • Compared with base, this PR adds session-host postmortem signal authority and its exit callback. After awaiting teardown, the callback unconditionally sets status to zero, overriding the exitAfterSessionDisposal failure path that sets process.exitCode = 1 when endpoint/readiness cleanup cannot be verified (sdk.ts:1350-1373).
    • Reachable case: a ready host receives SIGTERM or SIGINT and teardown cannot verify endpoint removal or readiness-marker revocation. The callback then reports a successful process exit although teardown recorded failure. This makes status-based parent/supervisor diagnostics unable to distinguish failed cleanup from success.
    • The broker does not rely on that exit code as closure proof: waitForClose checks host unregistration, endpoint absence, and observed process exit (packages/coding-agent/src/sdk/broker/lifecycle.ts:5405-5466) and returns uncertainty when required proof is missing. The host still attempts a failure diagnostic and receipt. Thus the defect is limited to status reporting and does not establish that the broker accepts incomplete cleanup; it is not a merge blocker.
    • Preserve the teardown result (for example, exit with process.exitCode ?? 0) rather than resetting it to zero.

Non-blocking Observations

  • The stale-marker regression test exercises retireExitedLifecycleMarkerPair directly, while the real-host resume test starts without a planted stale pair. An integration test that seeds an exited-owner marker pair and resumes through Broker.handleRequest("session.resume") would protect the wiring; the helper race tests and exact-head CI provide meaningful coverage, so this is optional.
  • The new retirement path repeats identity-bound exactUnlinkDirect assembly already encapsulated by removeLifecyclePublicationFile in packages/coding-agent/src/sdk/broker/lifecycle.ts:1920-1938. The launch and sweep eligibility rules differ and should remain explicit, but sharing the lower-level unlink primitive could reduce drift in security-sensitive identity/result handling. This is optional cleanup, not a blocker.

CI / Verification

Exact reviewed head dacbb628896f31033a8c078b9b696b076f051aca has 22 successful and 7 skipped check runs in Dev CI run 37133683243. Both changed SDK lifecycle test suites, the coding-agent check, affected-path aggregate, state-gates aggregate, and virtual integration validation succeeded. Skipped checks were conditional platform/input jobs, not product failures. No local tests were run during this static review.

Axis Coverage

Axis Verdict Coverage
A1 — Intent / Policy / Contract APPROVED Stale-marker fix follows lifecycle contracts; the signal exit-status mismatch is the non-blocking finding above.
A2 — Architecture / Correctness / Failure APPROVED Compared base and head across retirement ordering, owner identity, replacements, retries, and close consumers; no merge-blocking race found.
A3 — Security / Privacy / Trust APPROVED Cleanup is scoped to canonical session IDs and exact marker/file identities; no new privilege or data-exposure path found.
A4 — Verification / Tests / CI APPROVED Relevant tests and stable aggregates passed at the exact head; the missing seeded-resume integration is optional.
A5 — Context / Compatibility / Platform APPROVED Traced marker/endpoint producers and consumers and existing unlink abstractions; no blocking compatibility issue found.

Limitations

The review was static; no local tests or cross-platform execution were performed. Seven conditional checks were skipped. Branch-protection required-check configuration could not be independently retrieved (API returned 401), although the exact-head Dev CI aggregates succeeded.

@Yeachan-Heo
Yeachan-Heo merged commit e29e39e into dev Oct 3, 2026
53 of 56 checks passed
@Yeachan-Heo

Copy link
Copy Markdown
Owner

Merged into dev.

  • snowykr approved the exact head dacbb62 (earlier change request was on 58a2b66). CI on that head: 22 passed, 0 failed.
  • The branch was 13 commits behind dev, and fix(sdk-broker): stop per-poll heartbeat checkpoint + full index replay in readiness wait #6278 also changed sdk/broker/lifecycle.ts and sdk-broker-lifecycle-e2e.test.ts. So I tested the merge with dev 8e709f59 locally: it auto-merges cleanly; sdk-broker-lifecycle-e2e.test.ts 151 pass / 1 skip / 0 fail; sdk-broker-stale-ready-regression.test.ts 11/0; bun run check in packages/coding-agent exits 0.

—
[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo

Copy link
Copy Markdown
Owner

Follow-up: this broke one test outside its CI plan. sdk-machine-lifecycle-topology.test.ts still expected the ready marker to exist after the shared owner closes, so dev shard 3 has failed deterministically since the merge e29e39e2 (3/3 locally; it passes 3/3 on the parent). That was my merge, and my merged-tree check didn't run this file. Fix is in #6287: one assertion flipped to match #6265's intended behavior, the same change #6265 made in sdk-broker-lifecycle-e2e.test.ts.

—
[repo owner's gaebal-gajae (clawdbot) 🦞]

@probepark

Copy link
Copy Markdown
Collaborator Author

Dev CI regression after this merge (e29e39e2, run https://github.com/Yeachan-Heo/gajae-code/actions/runs/37142012902, test:@gajae-code/coding-agent:shard-3-of-8):

packages/coding-agent/test/sdk-machine-lifecycle-topology.test.ts - shared-agent equal saved IDs select one owner without cross-workspace effects in either adapter direction fails at :639-641. After closeSharedOwner(...) the test still expects <id>.lifecycle.ready.json to exist, but this PR now revokes the published readiness marker during graceful host stop (revokePublishedReadinessMarker in exitAfterSessionDisposal, src/commands/sdk.ts).

Evidence: the test blob 6634e11a422e is the same at the last green dev (8e709f59, where this test passed in shard-3) and at e29e39e2. src/commands/sdk.ts (6b68e032 -> 562cf5da) and src/sdk/broker/lifecycle.ts (62265141 -> 851be9f1) changed, and this PR was the only change in the range. PR CI selected only the touched test files, so shard-3 never ran the topology suite before merge. sdk-broker-lifecycle-e2e.test.ts was already updated in this PR to expect the ready marker gone (resolves -> rejects), so the topology assertion looks stale, not wrong behavior. A test-only follow-up against dev is being prepared.

suho-han pushed a commit to suho-han/gajae-code that referenced this pull request Oct 5, 2026
Yeachan-Heo#6265 (fixes Yeachan-Heo#6261) retires the lifecycle ready marker when the owner
closes; the shared-agent collision topology test still pinned the old
behavior and fails deterministically on dev since e29e39e.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants