Skip to content

Commit d34e11c

Browse files
A3Ackermanckumar1
andauthored
fix: start nudge-queue poller for all agents, including Claude (gt-dgf) (#3304)
Queued nudges to idle Claude agents were silently dropping. Root cause: a circular dependency between the nudge queue and Claude's UserPromptSubmit hook. Claude agents drain queued nudges via the UserPromptSubmit hook (gt mail check --inject). But that hook only fires when the agent submits a prompt. An idle agent waiting at the prompt never submits, so the hook never fires, and the nudge never drains. The agent waits for the nudge; the nudge waits for agent input. Deadlock. The nudge-queue poller was designed for exactly this case — it polls every 10s and delivers when the agent is idle. But it was only started for non-Claude agents, under the assumption that Claude's hook made it unnecessary. That assumption fails for idle agents. Fix: start the poller for ALL agents (crew, witness, refinery). The poller coexists safely with the UserPromptSubmit hook because Drain() is atomic — whoever drains first wins, the other sees an empty queue. The poller's idle detection is Claude-aware: checks for "esc to interrupt" busy indicator, requires prompt prefix visible with 2x confirmation, and skips drain entirely if the agent appears busy. Reported by the mayor. Bead: gt-dgf. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Executed-By: gastown/crew/woodhouse Co-authored-by: gastown/crew/woodhouse <cherub.chopra@gmail.com>
1 parent 6a120e0 commit d34e11c

3 files changed

Lines changed: 26 additions & 8 deletions

File tree

internal/crew/manager.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -878,14 +878,16 @@ func (m *Manager) Start(name string, opts StartOptions) error {
878878
_ = t.AcceptStartupDialogs(sessionID)
879879
}
880880

881-
// Start background nudge-queue poller for agents that lack turn-boundary
882-
// drain hooks (e.g., Gemini, Codex). Claude drains its queue via
883-
// UserPromptSubmit hook so it doesn't need the poller.
884-
if preset != nil && !preset.HasTurnBoundaryDrain {
885-
if _, pollerErr := nudge.StartPoller(townRoot, sessionID); pollerErr != nil {
886-
// Non-fatal — nudges may be delayed but the agent still works.
887-
style.PrintWarning("could not start nudge poller for %s: %v", name, pollerErr)
888-
}
881+
// Start background nudge-queue poller for ALL agents (gt-dgf).
882+
// Claude drains its queue via UserPromptSubmit hook, but that hook only
883+
// fires when the agent submits a prompt. Idle agents (waiting at prompt
884+
// for work) never submit, so queued nudges deadlock: agent waits for
885+
// nudge, nudge waits for agent input. The poller breaks this cycle by
886+
// polling every 10s and delivering when idle. Drain() is atomic so the
887+
// poller and UserPromptSubmit hook coexist safely.
888+
if _, pollerErr := nudge.StartPoller(townRoot, sessionID); pollerErr != nil {
889+
// Non-fatal — nudges may be delayed but the agent still works.
890+
style.PrintWarning("could not start nudge poller for %s: %v", name, pollerErr)
889891
}
890892
}
891893

internal/refinery/manager.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/steveyegge/gastown/internal/config"
1919
"github.com/steveyegge/gastown/internal/constants"
2020
"github.com/steveyegge/gastown/internal/git"
21+
"github.com/steveyegge/gastown/internal/nudge"
2122
"github.com/steveyegge/gastown/internal/rig"
2223
"github.com/steveyegge/gastown/internal/runtime"
2324
"github.com/steveyegge/gastown/internal/session"
@@ -239,6 +240,13 @@ func (m *Manager) Start(foreground bool, agentOverride string) error {
239240
return fmt.Errorf("waiting for refinery to start: %w", err)
240241
}
241242

243+
// Start nudge-queue poller (gt-dgf). Claude's UserPromptSubmit hook only
244+
// drains when the agent submits a prompt. Idle agents never submit, so
245+
// queued nudges deadlock. The poller breaks the cycle by polling every 10s.
246+
if _, pollerErr := nudge.StartPoller(townRoot, sessionID); pollerErr != nil {
247+
log.Printf("warning: could not start nudge poller for %s: %v", sessionID, pollerErr)
248+
}
249+
242250
_ = runtime.RunStartupFallback(t, sessionID, "refinery", runtimeConfig)
243251
_ = runtime.DeliverStartupPromptFallback(t, sessionID, initialPrompt, runtimeConfig, constants.ClaudeStartTimeout)
244252

internal/witness/manager.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/steveyegge/gastown/internal/beads"
1515
"github.com/steveyegge/gastown/internal/config"
1616
"github.com/steveyegge/gastown/internal/constants"
17+
"github.com/steveyegge/gastown/internal/nudge"
1718
"github.com/steveyegge/gastown/internal/rig"
1819
"github.com/steveyegge/gastown/internal/runtime"
1920
"github.com/steveyegge/gastown/internal/session"
@@ -246,6 +247,13 @@ func (m *Manager) Start(foreground bool, agentOverride string, envOverrides []st
246247
log.Printf("warning: tracking session PID for %s: %v", sessionID, err)
247248
}
248249

250+
// Start nudge-queue poller (gt-dgf). Claude's UserPromptSubmit hook only
251+
// drains when the agent submits a prompt. Idle agents never submit, so
252+
// queued nudges deadlock. The poller breaks the cycle by polling every 10s.
253+
if _, pollerErr := nudge.StartPoller(townRoot, sessionID); pollerErr != nil {
254+
log.Printf("warning: could not start nudge poller for %s: %v", sessionID, pollerErr)
255+
}
256+
249257
_ = runtime.RunStartupFallback(t, sessionID, "witness", runtimeConfig)
250258
initialPrompt := session.BuildStartupPrompt(session.BeaconConfig{
251259
Recipient: session.BeaconRecipient("witness", "", m.rig.Name),

0 commit comments

Comments
 (0)