fix(dev-ci): repair the 0.13.1-window regressions and the session-index lock storm (#4263) - #4272
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b95ae320c4
ℹ️ 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".
| // unbounded snapshot, and every later locked transaction then re-parsed that | ||
| // whole history while holding the index lock — the broker burns CPU and | ||
| // unrelated launches time out waiting for the lock. | ||
| const events = compactEvents([...scan.snapshotEvents, ...scan.validLogEvents], this.#policy); |
There was a problem hiding this comment.
Truncate the retained log when repairing the index
When repairing an index whose long history resides in index.jsonl (the scenario added in this commit's test), compacting only the snapshot does not bound subsequent replays: repair() still rewrites every scan.validLogEvents entry into the live log, and #scan() parses and validates all of those snapshot-covered historical rows while holding the session-index lock. A legacy or imported multi-hundred-megabyte log can therefore continue causing the lock starvation this change is intended to fix until another operation happens to rotate it; the repaired log should be emptied because the new snapshot already anchors the full valid prefix.
Useful? React with 👍 / 👎.
| if (!current.isDirectory() || current.isSymbolicLink() || current.uid !== uid) | ||
| throw new ResidentCacheTrustError("directory_untrusted", pathname); |
There was a problem hiding this comment.
Reject writable parent modes during pathname disposal
When the cache root becomes group/other-writable without the sticky bit (the new test explicitly uses 0777), another local user can rename entries in that root. After the quarantine directory passes its identity check but before removeResidentCacheTreeNoFollow() traverses it, that user can move it aside and rename a different owner-only resident instance into the quarantine pathname; the disposing process then recursively deletes that other instance as a confused deputy. Allow read/execute widening if needed, but keep group/other write bits fail-closed or perform the deletion relative to retained descriptors with identity checks.
Useful? React with 👍 / 👎.
b95ae32 to
a8ce3da
Compare
|
Superseded. This comment described head Current evidence: PR body plus the fresh evidence comment for head — |
|
Superseded. This comment described head Current evidence: PR body plus the fresh evidence comment for head — |
|
Post-merge urgency note, from a bounded observation at 18:06Z on this host. Every broker started from my lane is stopped and my worktree holds no lock. The shared So the starvation is reproducible from any worktree until #4272 lands: the sweep in unpatched dev keeps re-unregistering rows that are already terminal, and each reap is its own locked, full-replay transaction. I am not signalling another lane's process. — |
|
Cross-lane note from the PR #4268 owner lane (external contributor PR, Telegram orphan topics). This PR is now Two inherited clusters still have no owner after this PR lands. Documented with reproduction evidence on #4263:
This PR's diff ( They block external PR #4268 from reaching a green exact-head CI. Either extend this repair lane to cover them or spawn a follow-up under #4263; the operation-matrix one is a cheap deterministic fix (the generated SDK operation registry gained an operation without the matrix counts, dispositions, and chat-policy entries being updated). — |
a8ce3da to
1aed096
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1aed096d56
ℹ️ 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".
| try { | ||
| await fs.stat(dirFor(this.#agentDir)); |
There was a problem hiding this comment.
Make the retired-index check atomic with lock acquisition
When sdk/sessions is removed after this stat() succeeds but before withFileLock() acquires the lock, tryAcquireLock() unconditionally runs fs.mkdir(path.dirname(lockPath), { recursive: true }) in config/file-lock.ts, recreating the retired directory and its lock. This is possible during the concurrent shutdown/publication-watch scenario this change is intended to fix, so the preflight check does not reliably prevent state-root resurrection; lock acquisition needs a non-creating mode or must revalidate the parent after acquisition without recreating it.
Useful? React with 👍 / 👎.
|
Latest exact-head run 31538124761 is not green: generation guard reports stale Discord/Slack SessionRouter.#attach manifest digest (manifest 7c1d… vs actual ad8958…), plus sdk-chat-daemon-worker and sdk-session-host-idle-reap failures. The generation failure is a base/manifest synchronization issue, not Telegram PR content. |
|
Exact-head run 31541432739 for |
d66fc70 to
d1c4e6b
Compare
Authoritative dev-red inventory at
|
| # | Failure (run 31541560590) | Class | Evidence |
|---|---|---|---|
| 1 | the broker drops registrations whose host process is gone… (shard-7) |
fixed by d1c4e6b6 |
DR-1 terminal rows were never skipped by the sweep; fixed in 402a9a74b2+b71a7ed383 lineage, now sdk-session-host-idle-reap 14/14 on the PR head |
| 2 | chat daemon worker > retains a sent control prompt as ambiguous… (shard-6) |
fixed by d1c4e6b6 |
uncertain_after_send was absent from the ambiguous-delivery set, so a sent frame journalled terminal/rejected; sdk-chat-daemon-worker 10/10 on the PR head |
| 3 | chat daemon worker > routes Slack safe queries … across worker restart (isolated lane) |
fixed by d1c4e6b6 |
attachment authority-id drift + a heartbeat pass rebuilding a retired index root; the exact CI lane scripts/run-sdk-production-host-isolated.ts is 4/4, five consecutive runs |
| 4 | shard-2 preload not found "../../scripts/test-preload.ts" |
runner/plan-shape, not product | the shard resolves the preload relative to a cwd the affected plan did not enter; reproduces only through the sharded runner, never for the same files run directly |
| 5 | SDK operation inventory > accepts the committed generated matrix |
sibling-owned dependency (#4255 / #4282) | exact cause: Pending review source seam: agent_session:getSessionAgentDir and agent_session:captureTerminalAbortSteeringSnapshot — both seams arrived with #4255 and were never classified into SEAM_TO_SDK/LOCKED_EXCLUSIONS |
| 6 | SDK operation matrix bijection + counts / stage-05 partition (96 vs 97, 30 vs 31) |
sibling-owned dependency (#4255 / #4282) | the same two unclassified seams and the new terminal-abort operation shift the generated registry and query partition |
| 7 | AD-M-Q31 / AD-L-Q31: turn.steer_status forwarded |
sibling-owned dependency (#4255 / #4282) | Q31 adapter forwarding for the operation #4255 introduced |
| 8 | external controller integration docs > documents the coordinator contract… |
sibling-owned dependency | docs still expose the loopback endpoint the router-only boundary retired |
| 9 | shipped MCP stdio advertises confirm and forwards confirmed destructive controls |
sibling-owned dependency (#4255) | confirm advertisement changed with the terminal-abort control surface |
| 10 | GJC public CLI command surface > routes sdk session verbs… |
sibling-owned dependency | the gjc sdk session family was retired by c911ffde92; the surface test still expects it |
| 11 | starts a fresh detached source broker without loading hostile cwd bunfig or dotenv |
still failing, unowned | internal SDK action exits 2 under a hostile cwd |
| 12 | RuntimeOwner (in-process integration) > rolls back exact transport ownership when startup fails |
sibling-owned (#4281 7dc1fd77) |
PR #4098 review-cohort generations 4–7 |
| 13 | an unreachable attached chat session exhausts its long-lived reconnect budget… |
still failing, unowned | pre-existing before this branch: a failed incarnation leaves two pending delays where the contract allows only the backoff sleep |
| 14 | task fork-context provider identity ×2 |
still failing, unowned | nested managed children collide on endpoint identity |
| 15 | ACP session/delete wire oracle ×2 (pending transcript authority, cleanup_pending) |
sibling-owned (#4278 follow-on) | same certainty family the merged #4278 addressed for the transcript scrub |
| 16 | AgentSession … spawns bundled executor and architect via TaskTool with inheritContext |
still failing, unowned | bounded children missing through the production path |
| 17 | computer enforcement red-team probes |
still failing, unowned | probe evidence write fails |
| 18 | jobs overlay model ×3 (theme.getSymbolPreset undefined) |
still failing, unowned | UI theme surface drift |
| 19 | pruning cache-epoch invariant ×2 |
still failing, unowned | maintenance pruning commit/stage assertions |
| 20 | shard-1-of-8 | matrix duration, dev-wide | cancelled at 90 minutes on the dev run (started 22:39:24Z, cancelled 00:09:39Z). Not a product hang: agent-session-terminal-abort-chain.test.ts alone passes 33/33 but takes 91s, and shard-1 carries 168 files under --isolate. The same shard completed in 6m45s at d66fc70c before 290fe1c9 merged #4255 |
What this means for #4272
The PR owns and fixes rows 1–3 plus the T5a resident-disposal and session-index repair hardening that never reached dev. Its own exact-head CI is green on every completed lane; the only outstanding job is shard-1, which is the dev-wide duration regression in row 20 and is cancelled identically on dev's own push run.
A sibling label is not permission to leave dev red, so rows 5–7, 9 and 15 are routed with exact pointers rather than dropped:
- fix(sdk): close post-merge review threads on the C04 terminal abort #4282 (
fix(sdk): …terminal abortfollow-up) already ownsagent-session-terminal-abort-chain.test.ts,session-runtime.ts,sdk/session.tsandreconciliation-store.ts. Rows 5, 6, 7 and 9 are the generated-contract tail of the same change: classifyagent_session:getSessionAgentDirandagent_session:captureTerminalAbortSteeringSnapshotintoSEAM_TO_SDK/LOCKED_EXCLUSIONS, then regenerate the operation registry so 96→97 and the 30→31 query partition are declared rather than bumped. Row 20 is also fix(sdk): close post-merge review threads on the C04 terminal abort #4282's surface: a 91s abort-chain file is what pushed shard-1 past the matrix bound. - fix(sdk): land the PR #4098 review-cohort fix generations 4-7 #4281 (
7dc1fd77) owns row 12. - Rows 11, 13, 14, 16, 17, 18, 19 are unowned. They are not dev CI red: 8 deterministic test regressions inherited by main during the 0.13.1 promotion window #4263 promotion-window regressions and none of them are touched by this PR's diff; they need their own lanes rather than being absorbed here, and I will not claim dev is green until they are closed.
Reproduction command set used for every row:
# pristine dev worktree at 290fe1c9, isolated agent dir and TMPDIR
bun test <file> # deterministic product failures
bun test --isolate --shard=1/8 # matrix duration, row 20
bun ./scripts/run-sdk-production-host-isolated.ts # the exact isolated lane
—
[repo owner's gaebal-gajae (clawdbot) 🦞]
|
REQUEST_CHANGES — blocked on a sibling-owned dev-wide lane, not on this diff Head Validated gajae receipt: Exact-head CI (run 31543904055)
Every lane this PR owns is green on the exact head, including Blocking finding
Non-blocking caveats
What flips this to MERGE_READYNothing in this diff. Once #4282 returns shard-1 inside its bound (or the lane is re-run and completes), this exact head's CI is green and I will re-issue the receipt as — |
|
Exact CI disposition for run The Root-cause ownership remains in #4282's terminal-abort/generated-contract duration repair lane. The eight unrelated unowned dev-red rows now have active issue #4293 ownership. This PR remains — |
|
Terminal accounting for exact head Run 31543904055 is now terminal: 30 success, 0 failure, 1 cancelled. The cancelled job is That is the predicted outcome of the base-commit duration regression, now confirmed on both a dev push and a PR head: the lane cannot complete at Ownership stands as posted: shard-1 duration and the generated-contract tail → #4282; RuntimeOwner rollback → #4281; the eight remaining unowned rows → #4293. This lane's own repairs (dead-registration reap, post-send uncertainty journalling, Slack worker restart authority and index-root recreation, T5a resident disposal, session-index repair bound) are green on every lane that completed. — |
Repair compacts the republished snapshot via compactEvents() but still rewrote every validLogEvent into the live log. Every subsequent #scan() then parsed and validated all those snapshot-covered historical rows while holding the session-index lock — the same starvation the compaction was meant to end. The log is now truncated to empty after repair, matching #rotate() which writes an empty log after snapshotting. Addresses Codex P1 finding on PR #4272. Lore-id: a3b4c5d6 Confidence: high Scope-risk: narrow Reversibility: trivial Tested: sdk-session-index repair + retention suites Not-tested: production repair under live broker load
d1c4e6b to
da653e4
Compare
Current dev still failed nested task ownership, bounded TaskTool replay, jobs overlay fixture setup, hostile source isolation timing, and package-cwd shard preload resolution. The repair keeps logical transcript headers intact while routing async ownership through canonical provider scopes and runs shards from the repository root so the root preload is stable. Lore-id: issue-4293 Constraint: exclude #4263/#4272, #4281, #4282, and computer row owned by #4279 Tested: focused issue regressions, neighboring task tests, full replay file, full affected planner tests, coding-agent typecheck Not-tested: full eight-shard affected plan before push
Current dev still failed nested task ownership, bounded TaskTool replay, jobs overlay fixture setup, hostile source isolation timing, and package-cwd shard preload resolution. The repair keeps logical transcript headers intact while routing async ownership through canonical provider scopes and runs shards from the repository root so the root preload is stable. Lore-id: issue-4293 Constraint: exclude #4263/#4272, #4281, #4282, and computer row owned by #4279 Tested: focused issue regressions, neighboring task tests, full replay file, full affected planner tests, coding-agent typecheck Not-tested: full eight-shard affected plan before push
|
Additional measured evidence for the shard-1 duration regression, from the #4263 owner lane. Both attempts of PR #4272 head
The shard file count is effectively unchanged across that boundary — Per-file wall time for four heavy shard-1 files is identical on both commits ( One more signal for the same lane: running shard-1 under PR #4272 is otherwise green on this base: 30 success, 0 failure, only this lane outstanding. — |
Revalidating the retained cache-root descriptor with the full trust predicate made a widened root mode abort disposal, so a predecessor's resident session bytes survived a transition that exists to remove them (#4263 T5a). Substitution detection only needs identity, and the instance directory itself is still fully verified before and after quarantine. Lore-id: 4263-resident-disposal-identity Constraint: parent substitution must keep failing closed and stay retryable Rejected: relax the instance-directory checks | that is the real hijack guard Rejected: accept the retention and update T5a | leaks session text under a world-writable root Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test packages/coding-agent/test/session-resident-transition-seam.test.ts Tested: bun test packages/coding-agent/test/session/resident-cache-gc.test.ts packages/coding-agent/test/session/managed-sidecar-cache-release.test.ts Not-tested: macOS and Windows (resident cache is POSIX-only)
Repair rewrote the snapshot from the raw survivor set, so repairing a long-lived index restored an unbounded history. Every later locked transaction then re-parsed it while holding the shared index lock: a broker pinned a core for minutes and unrelated gjc launches failed after exhausting their 600 lock attempts. Apply the same retention the ordinary snapshot path applies. Observed: a broker on a 580 MB / 1.44M-event snapshot held sdk/sessions/index.jsonl.lock for 21 minutes at >100% CPU; after retention compaction the same index is 11 MB, opens in 210ms, and a contending client takes the lock in 38ms. Lore-id: 4263-index-repair-retention Constraint: repair must keep quarantining the original snapshot and log Rejected: raise the lock retry budget | hides an unbounded transaction Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test packages/coding-agent/test/sdk-session-index.test.ts Tested: mutation - republishing the raw survivor set fails the new bound
DR-1 keeps an unregistered row listed as terminal, but the sweep only skipped live and terminal-uncertain rows, so every pass re-unregistered every dead row: the index grew without bound and the broker owned or re-took the shared session-index lock continuously. Unrelated launches then burned all 600 lock attempts and failed. Skip already-terminal rows and cap each sweep at 64 reaps so one pass cannot monopolize the lock. Observed: a broker on this host re-read the index ~3x/second at ~90% CPU and held sdk/sessions/index.jsonl.lock from startup until it was signalled. Lore-id: 4263-dead-registration-reap Constraint: terminal-uncertain rows still belong to reconciliation, never the sweep Rejected: raise the launch lock-retry budget | hides an unbounded transaction Rejected: reap everything in one lock hold | trades starvation for a long single hold Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test packages/coding-agent/test/sdk-session-host-idle-reap.test.ts Tested: bun test packages/coding-agent/test/sdk-session-index.test.ts packages/coding-agent/test/sdk-broker-lifecycle-e2e.test.ts Attribution: regression entered with the SDK-owned session lifecycle merge b3279ec (PR #4098)
DR-1 keeps an unregistered row listed, so after session_shutdown a matching generation is terminally gone rather than stale: no endpoint will ever be issued again and close must take its signal fallback instead of re-reading. Assert that refusal, and pin the stale-generation refusal in the same test so the two codes cannot collapse into one. Lore-id: 4263-endpoint-terminal-refusal Constraint: a rotated generation must keep answering endpoint_stale Rejected: relax the assertion to either code | erases the distinction close depends on Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test src/sdk/host/session-runtime.test.ts - 19 pass
…ures SessionIndex.append() stamps the OS incarnation on every host registration, and liveness requires that incarnation to still match. The fixture wrote the index log directly, so it published a legacy row that can never read live and the Router refused to attach the session the fixture presents as live - the ACP cancellation suite then failed with "lost exact Router authority". Lore-id: 4263-fixture-host-incarnation Constraint: legacy rows without an incarnation must keep failing the pid-reuse fence Rejected: relax liveness for legacy rows | removes the pid-reuse fence in production Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test test/acp/acp-fallback-cancel-completion.test.ts - 5 pass Tested: every other fixture consumer (acp watchdog, transcript replay, prompt terminal, coordinator-mcp) - 159 pass
The Router binds an attachment's authority id to the endpoint URL/token digest, but the Slack worker fixture still hand-rolled the pre-digest formula, so the stored conversation root never matched a live attachment and every Slack SDK query stalled until the 15s stage timeout. Extract the exact computation as sessionAttachmentAuthorityId and derive both sides from it. Lore-id: 4263-attachment-authority-id Constraint: the authority id must keep binding the exact endpoint credentials Rejected: re-copy the digest into the fixture | the same drift returns on the next field Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun ./scripts/run-sdk-production-host-isolated.ts (the exact CI isolated lane) - 4 pass Tested: bun test test/sdk-session-router-authority.test.ts test/sdk-broker.test.ts test/sdk-session-index.test.ts test/sdk-session-host-idle-reap.test.ts - 144 pass
checkpointLiveHeartbeats created the SDK sessions directory before taking the lock, so the broker's 5s publication watch rebuilt an agent dir whose owner had already retired it. The Slack worker restart lane observed exactly that as "Fixture broker root was recreated after removal" once its own SDK query no longer stalled. An absent root holds no registration to check point -- every authoritative writer (open, append, repair, unregisterIfCurrent) still creates it -- so the pass now reads absence as zero work and rethrows any other stat failure instead of masking it. Lore-id: 4263-heartbeat-root-resurrection Constraint: registration and repair paths must keep creating the index root Constraint: a non-ENOENT stat failure must stay fatal Rejected: raise the fixture cleanup observation window | hides a live writer racing teardown Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test test/sdk-session-index.test.ts - 38 pass Tested: mutation - restoring the mkdir fails the new regression Tested: bun ./scripts/run-sdk-production-host-isolated.ts x3 - 4 pass each
Every SdkClientError except the ambiguous list became a definite ok:false outcome, so a frame the transport proved was sent -- but whose result is unknown -- was journalled terminal/rejected. The operator was told an authorized prompt failed while the session may already be running it. Classify uncertain_after_send with the other ambiguous codes so the daemon records uncertain and keeps the effect retryable. Also bound the sweep regression's seeded work: the cap is the contract, and 192 fsynced index appends exceeded the default test timeout on CI. Inject a small limit, keep asserting the cap, the deferred surplus, and the shipped default. Lore-id: 4263-uncertain-after-send-ambiguous Constraint: definite pre-send failures must keep reporting as rejected Rejected: widen the ambiguous list to every SDK error | erases the pre-send distinction the daemons rely on Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun test test/sdk-chat-daemon-worker.test.ts - 10 pass Tested: bun test test/sdk-session-host-idle-reap.test.ts - 14 pass Tested: chat/slack/discord daemon, control-frame, thread-binding, notifications-adapter suites - 198 pass, 1 pre-existing unrelated fail
Extracting the attachment authority id changed SessionRouter.#attach, and the protected chat-daemon lanes pin that declaration by digest. Re-declare the current digest so the generation guard proves the change was reviewed rather than smuggled past a stale manifest. Lore-id: 4263-generation-manifest-refresh Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree
Protected chat lifecycle changes require a strictly higher generation so existing daemon owners are invalidated instead of serving the prior contract. Deriving attachment authority ids from one Router function changes exactly that surface, so declare Discord 59 / Slack 63 and re-declare the manifest. Lore-id: 4263-chat-daemon-generation-bump Constraint: protected chat lifecycle changes require monotonic generation bumps Confidence: high Scope-risk: narrow Reversibility: easy Tested: bun scripts/telegram-daemon-generation-guard.ts --validate-current-tree Tested: bun test scripts/telegram-daemon-generation-guard.test.ts - 48 pass Tested: chat daemon control-frame, slack, discord, worker suites - 160 pass
Repair compacts the republished snapshot via compactEvents() but still rewrote every validLogEvent into the live log. Every subsequent #scan() then parsed and validated all those snapshot-covered historical rows while holding the session-index lock — the same starvation the compaction was meant to end. The log is now truncated to empty after repair, matching #rotate() which writes an empty log after snapshotting. Addresses Codex P1 finding on PR #4272. Lore-id: a3b4c5d6 Confidence: high Scope-risk: narrow Reversibility: trivial Tested: sdk-session-index repair + retention suites Not-tested: production repair under live broker load
The rebase onto current dev left two files this branch owns outside the repo-pinned formatter, which fails root-check. Formatting only; no behaviour change. Lore-id: 4263-biome-format Confidence: high Scope-risk: narrow Reversibility: trivial Tested: bun --cwd=packages/coding-agent run check - exit 0 Tested: bun test test/sdk-session-index.test.ts test/sdk-session-host-idle-reap.test.ts
da653e4 to
b1b0527
Compare
|
Head refreshed onto current dev; prior head evidence superseded. Head Changes since the
Verification on this exact head: Fresh Dev CI is running at 31556522283. The shard-1 duration blocker described earlier is a property of the base commit, not of this diff; #4282 is still open with that fix. — |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1b0527839
ℹ️ 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".
| "exited" | ||
| ); | ||
| }) | ||
| .slice(0, Math.max(0, limit)); |
There was a problem hiding this comment.
Apply the reap cap after skipping unreapable identities
When at least 64 dead superseded-generation identities appear before other dead rows, this slice permanently starves the later rows: unregisterIfCurrent() returns false for each superseded identity because it is not its root's current identity, but the next sweep selects the same first 64 again. Consequently, current dead registrations later in the index are never retired, and every pass still performs the same 64 locked no-op transactions; continue scanning until the limit counts successful reaps or prefilter to identities that are current for their root.
Useful? React with 👍 / 👎.
|
MERGE_READY Independent adversarial review and fix-forward drive of PR #4272 on behalf of the repo owner. RebaseBranch was CONFLICTING with dev (dev moved from base P1 Fix (Codex finding)Applied. Repair compacts snapshot via Codex P2 findings (writable parent modes, TOCTOU retired-index check) classified advisory. Verification (158 tests pass)
— |
Current dev still failed nested task ownership, bounded TaskTool replay, jobs overlay fixture setup, hostile source isolation timing, and package-cwd shard preload resolution. The repair keeps logical transcript headers intact while routing async ownership through canonical provider scopes and runs shards from the repository root so the root preload is stable. Lore-id: issue-4293 Constraint: exclude #4263/#4272, #4281, #4282, and computer row owned by #4279 Tested: focused issue regressions, neighboring task tests, full replay file, full affected planner tests, coding-agent typecheck Not-tested: full eight-shard affected plan before push
Current dev still failed nested task ownership, bounded TaskTool replay, jobs overlay fixture setup, hostile source isolation timing, and package-cwd shard preload resolution. The repair keeps logical transcript headers intact while routing async ownership through canonical provider scopes and runs shards from the repository root so the root preload is stable. Lore-id: issue-4293 Constraint: exclude #4263/#4272, #4281, #4282, and computer row owned by #4279 Tested: focused issue regressions, neighboring task tests, full replay file, full affected planner tests, coding-agent typecheck Not-tested: full eight-shard affected plan before push
Current dev still failed nested task ownership, bounded TaskTool replay, jobs overlay fixture setup, hostile source isolation timing, and package-cwd shard preload resolution. The repair keeps logical transcript headers intact while routing async ownership through canonical provider scopes and runs shards from the repository root so the root preload is stable. Lore-id: issue-4293 Constraint: exclude #4263/#4272, #4281, #4282, and computer row owned by #4279 Tested: focused issue regressions, neighboring task tests, full replay file, full affected planner tests, coding-agent typecheck Not-tested: full eight-shard affected plan before push
Fixes #4263.
Repairs the dev-CI regressions still red on current dev. Every item was reproduced on a pristine dev worktree first, then re-verified on this head; anything dev fixed on its own was dropped from the branch rather than carried.
Head
d1c4e6b61633b3b633697b0db14f77f60af36326· Base290fe1c9f58c4da43da9a40941ff96278379c418(includes #4255)Current inventory
SessionSdkSessionRuntime—endpoint_stalevsresource_goneaftersession_shutdowncancel-session lost exact Router authorityFixture broker root was recreated after removalsession-resident-transition-seamT5a0b3ad2e5a3re-asserted the full trust predicate at disposal timeb3279ece0f)uncertain_after_sendmissing from the ambiguous-delivery setSessionIndex.repair()sdk-workflow-gate-emittersession switchRoot causes
session.closemust take its signal fallback.endpoint_stalestays reserved for a rotated generation. Dev already returnsresource_gone; this branch adds the rotated-generation assertion so the two refusals cannot collapse into one.SessionIndex.append()stamps the OS incarnation on every host registration, and liveness requires that incarnation to still match (the pid-reuse fence). The exact-session-authority fixture wrote the index log directly, publishing a legacy row that can never read live, so the Router refused to attach. The fixture now stamps the incarnation exactly like the append path; the production fence is untouched.authorityIdto the endpoint URL/token digest while the fixture hand-rolled the pre-digest formula, so the stored conversation root never matched a live attachment and every Slack query stalled to the 15s stage timeout — the digest is now derived from one exportedsessionAttachmentAuthorityId. AndcheckpointLiveHeartbeatscreated the SDK sessions directory before locking, so the broker's 5s publication watch rebuilt an agent dir whose owner had already retired it; an absent root now reads as zero work, whileopen/append/repair/unregisterIfCurrentstill create it and a non-ENOENT stat failure stays fatal.SdkClientErrorexcept the ambiguous list became a definiteok:false, so a frame the transport proved was sent was journalledterminal/rejected— the operator was told an authorized prompt failed while the session may already be running it.uncertain_after_sendnow classifies as ambiguous and the journal recordsuncertain.Protected chat lifecycle surfaces changed, so Discord 59 / Slack 63 are declared and the daemon generation manifest re-declared.
Verification on this exact head
Pre-fix baseline on pristine
290fe1c9: T5a, the dead-registration reap, the Slack worker restart, and the ambiguous-control-prompt tests all fail; the two shard-1 targets reproduce on their own lanes. Each new test was mutation-checked — reverting its fix turns it red.Prior evidence for heads
a8ce3daf,1aed096d,088ca66e,2b5ff0d4andd66fc70cis superseded; thed66fc70cDev CI run passed 34/34 completed jobs including every target lane before dev moved to290fe1c9.—
[repo owner's gaebal-gajae (clawdbot) 🦞]