Repository navigation
fix(core): keep subagent worker lock off child-runtime I/O - #259
Merged
Merged
Conversation
Supervisor.Shutdown could exceed its deadline, and Cancel/Dismiss could block behind a slow child runtime, because worker.mu was held across runtime Close, Steer, Transcript, and ConfigureThinking, while cancelWorker needed it before Shutdown reached its bounded wait. worker.mu now guards only short state flips. Runtime calls take a lease under the lock and run without it; execute stops new leases, waits for in-flight calls to return, then closes the runtime unlocked. Shutdown relies on s.cancel to cancel every worker context instead of a serial per-worker pass before its select. The cancellation generation is now advanced with a compare-and-swap so claim reconciliation cannot overwrite a newer Cancel/Dismiss generation with a value it read earlier (#245). A finished worker kept its conversation lock through parent notification (MailboxAdded), so a model change made as soon as the conversation settled could fail with ErrConflict. The worker now releases the lock after the durable completion and child-store cleanup, before leaving the running set (#246). Closes #244 Closes #245
This was referenced Oct 22, 2026
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.
First of three concurrency-hardening PRs.
Changes
worker.muno longer wraps child-runtime I/O, soShutdown(ctx)honors its deadline andCancel/Dismissdon't wait behind a slow runtime.Steer,TranscriptandConfigureThinkingtake a runtime lease under the lock and call the runtime without it.executerefuses new leases, waits for in-flight calls to return (each is bounded by its caller's context), then callsCloseunlocked.Shutdowndrops the serialcancelWorkerpass before itsselect;s.cancel(errSupervisorStop)already cancels every worker context.MailboxAdded, so a model change made as soon as the conversation settled could getErrConflict. That's what madeTestSupervisorPreparesSettledModelChangeAndPreservesThinkingflaky: a 50 ms delay after therunningremoval reproduces it 3/3. The lock is now released after the durable completion and child-store cleanup, before the worker leavesrunning.ChildRuntimecontract and the go-concurrency skill (lock order; removed the known exceptions this PR fixes).Tests
New file
internal/subagent/supervisor_lifecycle_test.go. Each regression test fails deterministically onmainand passes here; the waits use channel gates, not timing:Closeblocks: blocked >5 s onmain.Steer: blocked onmain.subagent state conflictonmain.Closewaits for an in-flightSteer. Removing the drain makes it fail.Validation
macOS arm64, disposable
KIT_HOME:gofmt -l .(clean),go build ./...,go vet ./....go test -race -count=50 ./internal/subagent/: pass.go test -race -count=5 ./internal/subagent/ ./internal/session/: pass.go test -race -count=20 -run TestSupervisor ./internal/subagent/after review fixes: pass.go test ./...andgo test -race ./...: everything passes exceptTestObserverPRCompletionPushesWithoutGitPollOrClientRead, a known flake (Flaky: TestObserverPRCompletionPushesWithoutGitPollOrClientRead times out under full-suite load #236) that the next PR fixes.The code-reviewer subagent found nothing blocking. Its nits are addressed:
Steernow releases its lease withdefer, and a probe test fails instead of hanging if the bug returns. It also found a pre-existing dismissal/cleanup window, filed as #258.#246stays open until the plugin half is resolved in the third PR.Closes #244
Closes #245