fix: MCP child sessions can be stranded in *_sending permanently #457
Labels
No labels
backend
bug
chore
duplication
effort:complex
effort:medium
effort:trivial
enhancement
follow-up
frontend
fullstack
priority:high
ready-for-agent
refactor
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
dries/ocman#457
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Code-quality audit (P2).
internal/mcp/tools_comm.go:152,163.A child session can end up permanently stuck in
*_sendingwith no way out.Two paths leave the row in
ChildResultAsyncSending/ChildResultWaitSending:_-discarded, so the caller sees only "sending message to child" and has no idea the row is now wedged.CompleteChildFollowuperrors — no restore is attempted at all.The guard at
tools_comm.go:119only acceptsdelivered/detached:so no subsequent request can ever claim that child again. The only recovery is editing
state.dbby hand.Related gap: the CAS primitives are untested
internal/state/childsessions.go:74,87,103—ClaimChildFollowup/CompleteChildFollowup/RestoreChildFollowupare the compare-and-swap primitives AGENTS.md calls "atomic" and race-critical, and all three are at 0.0% coverage.RestoreChildFollowupadditionally discardsRowsAffected:A missed CAS (0 rows updated) is indistinguishable from success — which is exactly the case the caller above needs to detect.
Suggested fix
RestoreChildFollowupreturns(bool, error)like its siblings; a missed CAS is reported.CompleteChildFollowuperror path too.*_sendingrows todelivered.Acceptance criteria
*_sendingwithout either a successful restore or a surfaced error.*_sendingrows are reclaimed automatically.Effort: M.
Validated against main @
2d661364: downgraded — core claim fixed. The 5s watcher sweep now recovers stuck rows (internal/server/mcp_watcher.go:88-97: any *_sending row whose waiter is no longer Registered is CASed back to async_pending/waiting), and both failure paths Unregister first, so "permanent, hand-edit state.db" no longer holds. In-request lost-CAS recovery also exists (tools_comm.go:170-179). Coverage claim is stale: TestSendMessageToChild_RestoresCompletionWhenSendFails and friends exercise Claim/Complete/Restore. Residue worth a small cleanup: RestoreChildFollowup still discards RowsAffected (childsessions.go:103-113, now benign) and tools_comm.go:153 still_ =-discards the restore error. Treat as low-priority hygiene, not a bug.