From 9e3eb094c51b027e18192e84c28b94eba8a8baef Mon Sep 17 00:00:00 2001 From: slit Date: Thu, 22 Jan 2026 12:22:32 -0800 Subject: [PATCH] fix(done): use ResolveHookDir for dispatcher lookup (sc-g7bl3) When a polecat runs gt done after work is complete, it should notify the dispatcher (the agent that slung the work). This notification was failing silently when the polecat's worktree was deleted before gt done finished. The issue was that getDispatcherFromBead() used ResolveBeadsDir(cwd) which relies on the polecat's .beads/redirect file. If the worktree is deleted (e.g., by Witness cleanup), the redirect file is gone and bead lookup fails. Fix: Use ResolveHookDir(townRoot, issueID, cwd) instead. ResolveHookDir uses prefix-based routing via routes.jsonl which works regardless of worktree state. This ensures dispatcher notifications are sent reliably even when the worktree is cleaned up before gt done completes. Co-Authored-By: Claude Opus 4.5 --- internal/cmd/done.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/cmd/done.go b/internal/cmd/done.go index 8d0c7694..359498ef 100644 --- a/internal/cmd/done.go +++ b/internal/cmd/done.go @@ -456,7 +456,7 @@ notifyWitness: // Notify dispatcher if work was dispatched by another agent if issueID != "" { - if dispatcher := getDispatcherFromBead(cwd, issueID); dispatcher != "" && dispatcher != sender { + if dispatcher := getDispatcherFromBead(townRoot, cwd, issueID); dispatcher != "" && dispatcher != sender { dispatcherNotification := &mail.Message{ To: dispatcher, From: sender, @@ -645,7 +645,7 @@ func updateAgentStateOnDone(cwd, townRoot, exitType, _ string) { // issueID unus if _, err := bd.Run("agent", "state", agentBeadID, "awaiting-gate"); err != nil { fmt.Fprintf(os.Stderr, "Warning: couldn't set agent %s to awaiting-gate: %v\n", agentBeadID, err) } - // ExitCompleted and ExitDeferred don't set state - observable from tmux + // ExitCompleted and ExitDeferred don't set state - observable from tmux } // ZFC #10: Self-report cleanup status @@ -678,12 +678,19 @@ func getIssueFromAgentHook(bd *beads.Beads, agentBeadID string) string { // getDispatcherFromBead retrieves the dispatcher agent ID from the bead's attachment fields. // Returns empty string if no dispatcher is recorded. -func getDispatcherFromBead(cwd, issueID string) string { +// +// BUG FIX (sc-g7bl3): Use townRoot and ResolveHookDir for bead lookup instead of +// ResolveBeadsDir(cwd). When the polecat's worktree is deleted before gt done finishes, +// ResolveBeadsDir(cwd) fails because the redirect file is gone. ResolveHookDir uses +// prefix-based routing via routes.jsonl which works regardless of worktree state. +func getDispatcherFromBead(townRoot, cwd, issueID string) string { if issueID == "" { return "" } - bd := beads.New(beads.ResolveBeadsDir(cwd)) + // Use ResolveHookDir for resilient bead lookup - works even if worktree is deleted + beadsDir := beads.ResolveHookDir(townRoot, issueID, cwd) + bd := beads.New(beadsDir) issue, err := bd.Show(issueID) if err != nil { return ""