Repository navigation
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
…first attach's preparation (VC-799 review) - recover() skips a child whose first outcome is a failed attach with no turn after it and whose queue still holds the task, and reads a held task's own run as the outcome once it ran - a first attach's preparation waits under a 10 min ceiling of its own, recorded as configuration_invalid - a durably refused kickoff (KickoffRefusedError) is reported, and left to the watcher's earliest-outcome rule - tests wait past the bound from when prepare() is reached - comments describe behaviour; one watch expression in session_start
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
session_startandsession_delegatesent their kickoff only after a READY first attach and stored it nowhere else. When the first attach failed, the Session existed with no task. The tool said "a person can retry it from the app", but a Retry attached an empty Session: no turn ran and no notice came back.session_sendrefused it (no live executor), and boot recovery skipped the child asnot-startedforever.The most common trigger was the 30 s admission bound on a first attach's workspace preparation.
#attachforced afollow-up:prefix onto theprepare()wait, so every first attach was bounded like a queued follow-up. The bound does not cancelprepare(), which goes on to finish, but the attach was still recorded as failed withadapter_unrecoverable.A second, smaller issue: on the ready path, the facade's kickoff port ignored the runtime's receipt, so a kickoff the runtime refused was never reported.
Fix
needs-recovery,startSessionOperationandDelegations.delegatesubmit the kickoff withdelivery: "queue"under the existing durable kickoff ids (<op>:kickoff/<op>:kickoff-message). It goes into the Session's own durable follow-up queue. The attach-failure Attention keeps that queue from draining. The next successful attach (the app's Retry) clears the Attention and the queue delivers the message as the first turn, the same wayautomations/run.tsowes its first message (VC-220/227). A person also sees it in the queue meanwhile.session_start: on a held kickoff, the caller's turn watch is armed now, so the Retry's run reports back the way a ready start does.session_delegate: no watch is armed while the child is held. When the held task's turn starts, the existingrearmwake (agents.ts) watches it. The delegation is still unanswered, so the one notice lands under<op>:answer.Delegations.recovertreats anattachment.failedthat comes first as no outcome when no turn has started since and the child's queue still holds<op>:kickoff-message. It counts the child as skipped, so the Retry's notice is the only one. Once the held task has run, recovery reads that run's own outcome rather than the failed attach before it.session-runtime.ts). This applies to a non-queue attach on a Session with no opened attachment.firstAttachPreparationTimeoutMs) instead of the 30 s admission bound. Past that, it fails aslocation_unavailable/configuration_invalid("Workspace preparation did not finish within 600 s."), the same Attention as any preparation that fails.kickoffSubmitter(the facade'ssubmitKickoffMessage) rejects with aKickoffRefusedError, carrying the refusal's code and detail, when the receipt isrejected. Anunknownreceipt is not a refusal.kickoff was not delivered).unsentbranch keeps the old wording. The VC-659 attach-failure reason (VC-659: adopt stacked ticket branches; name why a start's attach failed #879) is passed through unchanged.Decisions
prepare(), so it only failed the attach. A re-attach would add a second attach path.prepare()waits on things the git timeouts do not bound: slot waits, the per-repo worktree turn,copyIncludedFilesandmaterializeBlobs. So the first attach gets a generous ceiling rather than none.rearmonturn.startedcovers it under the delegation's own:answerids.needs-recovery. The Session holds real pending work that a Retry runs, so it should keep counting toward the 3 slots.Review
From the review of
a36fdffe. Changed inaf409116:recover()skips a child whose first outcome isattachment.failed, with noturn.startedafter it and<op>:kickoff-messagestill in its queue. A failed queue read is reported and falls back to notifying, so the parent is still told rather than never told.turn.starteddoes follow the failed attach, the held task ran. Recovery then takes the first outcome after that turn as the delegation's outcome, matching what the liverearmwatch would have reported. Otherwise a relaunch during the held task's run would notify "failed".delegations.recover(store.listUnansweredSubagents())before the Retry: skipped, with no notice. After the Retry there is exactly one notice under<op>:answer, and nothing is left unanswered. With the fix disabled, this test fails (reported: 1).configuration_invalid(engine test added). The Decisions entry now states the remaining limit, the quit-during-preparation gap, instead of "git's own timeouts".KickoffRefusedErroris onlyreported on the delegate ready path, and any other error still settlesfailed. A unit test covers it: a refused kickoff followed by a stop is reported asstopped.reachedpromise thatprepare()resolves, then sleep past the bound.session_startcomputeswatchOpenedSession(...)once, for ready or held.session_startdoor test now silences the error line it expects.bdda85a4: CI onaf409116failedstacked-branch-attach.integration.test.ts, which asserted no kickoff on a refused attach. It now asserts the kickoff is held (delivery: "queue",kickoff: "held").Declined:
watchOpenedSession, which every opening and steering tool shares. Every in-memory watch is lost on a relaunch, ready starts included. Saying so for this one door only would be inconsistent, and it can't be done cleanly from here: when nothing is watched, the shared line already says nothing reports back. If wanted, it belongs inwatchOpenedSessionas its own change.heldafter the kickoff ran or was removed): cosmetic. The queue answers the replayed command from its record, and telling the cases apart would need a second queue read on every replay for a field nobody acts on.Tests
Regression tests fail on
origin/mainsource and pass with this change. I checked by restoring main's five source files with the new tests left in place: 3/3 integration cases and the first engine case failed. For the MUST-1 case, disabling the recovery skip fails the delegate integration test.packages/host-core/src/session-runtime/kickoff-hold.integration.test.ts(new) uses the real Sessions facade, Session runtime, SQLite ledger and follow-up queue, start and delegate operations, delegation store and wake bus. Only the executor is scripted.readyand the custom kickoff runs once, with no Attention.<op>:kickoff-message. A replayed start holds no second copy. The Retry delivers it exactly once, and a later replay plus a recovery sweep send nothing more.session_delegateon a refused first attach:<op>:answer, with nothing reported.packages/session-engine/src/session-follow-ups.test.ts:configuration_invalid;start-session.test.ts,delegate-session.test.ts,agent-tool-door.test.tscover:delivery: "queue";held/unsentoutcomes, and the watch on a held start;KickoffRefusedErrorfromkickoffSubmitter, and the watcher settling a refused kickoff.Run locally at
af409116(plusstacked-branch-attach.integration.test.ts: 2 passed atbdda85a4), one file at a time with--maxWorkers=1through the machine's serial runner:session-follow-ups.test.ts: 101 passedkickoff-hold.integration.test.ts: 4 passeddelegate-session.test.ts: 52 passedstart-session.test.ts: 31 passedagent-tool-door.test.ts: 59 passedvp checkon the 10 touched files: cleanCI is the full proof. The
af409116run also hit a 5 s timeout inworktree/watch.test.ts("500 Done tickets … 10 gh calls a cycle", 5.4 s). That file and the code it covers are untouched here, and the timeout is not on the known-flake list. Thebdda85a4run is a fresh run for the new commit, not a rerun.Overlap:
git merge-treeagainstorigin/mainis clean, and so are #909, #919, #920, #921 and #922, none of which touch these files. #879 (VC-659) is already on main. Targets main directly.