You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Deacon cold-start boot prompt misroutes mol-deacon-patrol to a rig → spawns a throwaway polecat per cycle that dies at gt patrol report (Deacon never self-patrols) #4312
On every cold-start the Deacon's hardcoded boot prompt runs gt sling mol-deacon-patrol deacon. gt sling treats deacon as an explicit target, which is rejected (a non-rig target → deferred dispatch requires a rig target). The Deacon then "recovers" by slinging to a rig
(gt sling mol-deacon-patrol <rig>), which spawns a throwaway polecat and hooks the deacon-patrol
onto it. The polecat runs the full ~26-step deacon patrol in the wrong role and dies at the role-gated
final step (gt patrol report → "unsupported role for patrol report: polecat"). Net effect: the
Deacon never runs its own patrol (it babysits the polecat in a Monitor loop) and a fresh Claude
polecat session is wasted every cycle (session leak); the Witness re-dispatches the patrol to a new
polecat next cycle.
The fix is small and uses code that already exists: gt sling already self-targets on an empty/dot target (internal/cmd/sling.go — "empty/dot target = self-sling" → resolveSelfTarget()),
and resolveSelfTarget (internal/cmd/sling_target.go) already maps RoleDeacon → "deacon/". So gt sling mol-deacon-patrol (no target) would hook the patrol onto the Deacon itself with no rig spawn —
exactly the intended behavior. The boot prompt simply passes the wrong argument.
Distinct from #3763, which reports the same broken command but as a cross-DB hookBeadWithRetry
resolution failure and proposes wiring the resolver — that would make the wrong (explicit-target)
sling succeed, not stop the misroute-to-polecat. Resolving #3763 alone does not fix this.
Steps to Reproduce
Cold-start the town (gt up) so the daemon spawns the Deacon with its boot prompt; the Deacon's hook is empty (normal on cold start).
The Deacon follows the prompt → gt sling mol-deacon-patrol deacon → Error: deferred dispatch requires a rig target.
The Deacon recovers by slinging to a rig → gt sling mol-deacon-patrol <rig> → Target is rig '<rig>', spawning fresh polecat... (assignee <rig>/polecats/<polecat>).
The polecat runs the full deacon patrol; the await-signal step warns Could not read agent bead: ... no issue found matching "hq-deacon", and the final gt patrol report fails with unsupported role for patrol report: "polecat". The polecat files a bug and runs gt done.
Repeats every cycle — a fresh throwaway polecat per Deacon patrol cycle.
Expected Behavior
On cold-start the Deacon runs mol-deacon-patrolin its own (role=deacon) session via the existing
self-sling path; no polecat is spawned for the deacon patrol; gt patrol report succeeds.
Actual Behavior
The boot prompt's explicit-target gt sling mol-deacon-patrol deacon fails; the Deacon misroutes the
patrol to a rig, a throwaway polecat is spawned per cycle and can never complete the role-gated
patrol, and the Deacon never executes its own patrol loop.
Environment
OS: Linux
Go version: go1.26.0 (source build)
Gas Town version: v1.2.0 (both boot-prompt variants confirmed via strings on the binary). Still present on main — internal/cmd/deacon.go:529 and internal/deacon/manager.go:113 carry the literal gt sling mol-deacon-patrol deacon.
tmux version: n/a
Logs / Error Output
# Daemon→Deacon boot prompt (compiled string literal, deacon.go:529 / deacon/manager.go:113):
# "...check gt hook, and if it is empty run `gt sling mol-deacon-patrol deacon`, then execute the hook it creates."
$ gt sling mol-deacon-patrol deacon
Error: deferred dispatch requires a rig target: gt sling mol-deacon-patrol
$ gt sling mol-deacon-patrol <rig>
Target is rig '<rig>', spawning fresh polecat...
→ wisp <wisp-id> [HOOKED] Assignee: <rig>/polecats/<polecat> Type: molecule
# await-signal step (formula hardcodes the deacon agent bead):
gt mol step await-signal --agent-bead hq-deacon ...
⚠ Could not read agent bead: Error fetching hq-deacon: no issue found matching "hq-deacon"
# final role-gated report step:
$ gt patrol report --summary "..."
Error: unsupported role for patrol report: "polecat"
Additional Context
Root cause. The boot prompt is a compiled Go string literal in the binary (deacon.go:529, deacon/manager.go:113) — no settings file, env var, or formula overlay can change it (the daemon emits
it before any formula loads). The role guard at internal/cmd/patrol_report.go is correct; the defect
is that dispatch lets a polecat ever reach it.
(B) Dispatch-time role guard — defense-in-depth.gt sling should refuse a role-scoped patrol
formula (mol-deacon-patrol / mol-witness-patrol / mol-refinery-patrol) targeted at a rig/polecat,
failing fast (e.g. Error: mol-deacon-patrol is a deacon-role formula and cannot be slung to a rig/polecat)
instead of spawning a doomed polecat. (A role / dispatch: self-only field on the formula metadata
would also protect the analogous witness/refinery misroutes.)
(C) Dynamic agent bead. Replace the hardcoded --agent-bead hq-deacon in the await-signal step
with a dynamic {{agent_bead}} to remove the latent boot-time race (the deacon bead may not yet exist
when a misrouted polecat queries it).
Operator workaround (for anyone hitting this before a fix lands). A PreToolUse hook guard that
blocks gt sling …mol-*-patrol… for the deacon/witness/refinery roles (role-aware via GT_ROLE),
plus a role directive that bootstraps patrol via the wisp path, contains it (zero throwaway polecats once
synced). Caveat: the first cold-start after a fresh binary deploy boots the Deacon before the hooks
re-sync, so the runaway fires once per deploy until the guard persists — which is why the in-binary fix
(A) matters.
Related:#3763 (same broken command, framed as cross-DB resolution — different fix; resolving it does
not stop this misroute) · #3699 (closed — town-level agents hook correctly; the self-sling path fix (A) uses) · #3839 / #4050 (closed — gt sling self-targeting hardening; the machinery fix (A) relies on) · #3148 (Deacon
boot patrol escalation flood) · #2386 (Deacon patrol fast-loop) · #3675, #4023 (deacon-patrol cluster).
Bug Description
On every cold-start the Deacon's hardcoded boot prompt runs
gt sling mol-deacon-patrol deacon.gt slingtreatsdeaconas an explicit target, which is rejected (a non-rig target →deferred dispatch requires a rig target). The Deacon then "recovers" by slinging to a rig(
gt sling mol-deacon-patrol <rig>), which spawns a throwaway polecat and hooks the deacon-patrolonto it. The polecat runs the full ~26-step deacon patrol in the wrong role and dies at the role-gated
final step (
gt patrol report→ "unsupported role for patrol report: polecat"). Net effect: theDeacon never runs its own patrol (it babysits the polecat in a Monitor loop) and a fresh Claude
polecat session is wasted every cycle (session leak); the Witness re-dispatches the patrol to a new
polecat next cycle.
The fix is small and uses code that already exists:
gt slingalready self-targets on anempty/dot target (
internal/cmd/sling.go— "empty/dot target = self-sling" →resolveSelfTarget()),and
resolveSelfTarget(internal/cmd/sling_target.go) already mapsRoleDeacon → "deacon/". Sogt sling mol-deacon-patrol(no target) would hook the patrol onto the Deacon itself with no rig spawn —exactly the intended behavior. The boot prompt simply passes the wrong argument.
Steps to Reproduce
gt up) so the daemon spawns the Deacon with its boot prompt; the Deacon's hook is empty (normal on cold start).gt sling mol-deacon-patrol deacon→Error: deferred dispatch requires a rig target.gt sling mol-deacon-patrol <rig>→Target is rig '<rig>', spawning fresh polecat...(assignee<rig>/polecats/<polecat>).await-signalstep warnsCould not read agent bead: ... no issue found matching "hq-deacon", and the finalgt patrol reportfails withunsupported role for patrol report: "polecat". The polecat files a bug and runsgt done.Expected Behavior
On cold-start the Deacon runs
mol-deacon-patrolin its own (role=deacon) session via the existingself-sling path; no polecat is spawned for the deacon patrol;
gt patrol reportsucceeds.Actual Behavior
The boot prompt's explicit-target
gt sling mol-deacon-patrol deaconfails; the Deacon misroutes thepatrol to a rig, a throwaway polecat is spawned per cycle and can never complete the role-gated
patrol, and the Deacon never executes its own patrol loop.
Environment
stringson the binary). Still present onmain—internal/cmd/deacon.go:529andinternal/deacon/manager.go:113carry the literalgt sling mol-deacon-patrol deacon.Logs / Error Output
Additional Context
Root cause. The boot prompt is a compiled Go string literal in the binary (
deacon.go:529,deacon/manager.go:113) — no settings file, env var, or formula overlay can change it (the daemon emitsit before any formula loads). The role guard at
internal/cmd/patrol_report.gois correct; the defectis that dispatch lets a polecat ever reach it.
Suggested fix (in order):
gt sling mol-deacon-patrol deaconwith the no-target self-slinggt sling mol-deacon-patrol(or
gt sling mol-deacon-patrol .).gt sling's empty/dot-target path already routes throughresolveSelfTarget()→deacon/, hooking the patrol onto the Deacon with no rig spawn — exactly theintended behavior. The town-level self-sling machinery this relies on is already in place and proven:
gt sling assigns bare agent name, gt hook queries with trailing slash — town-level agents never hook #3699 fixed bare-name-vs-trailing-slash hooking so town-level agents (mayor/deacon) hook correctly,
and gt sling self-targeting injects ack text into caller's prompt and gets interrupted #3839 / fix: gt sling self-targeting injects ack text into caller's prompt and gets interrupted #4050 hardened self-target prompt-injection. So (A) is a one-line literal change on top
of already-merged code.
gt slingshould refuse a role-scoped patrolformula (
mol-deacon-patrol/mol-witness-patrol/mol-refinery-patrol) targeted at a rig/polecat,failing fast (e.g.
Error: mol-deacon-patrol is a deacon-role formula and cannot be slung to a rig/polecat)instead of spawning a doomed polecat. (A
role/dispatch: self-onlyfield on the formula metadatawould also protect the analogous witness/refinery misroutes.)
--agent-bead hq-deaconin theawait-signalstepwith a dynamic
{{agent_bead}}to remove the latent boot-time race (the deacon bead may not yet existwhen a misrouted polecat queries it).
Operator workaround (for anyone hitting this before a fix lands). A
PreToolUsehook guard thatblocks
gt sling …mol-*-patrol…for the deacon/witness/refinery roles (role-aware viaGT_ROLE),plus a role directive that bootstraps patrol via the wisp path, contains it (zero throwaway polecats once
synced). Caveat: the first cold-start after a fresh binary deploy boots the Deacon before the hooks
re-sync, so the runaway fires once per deploy until the guard persists — which is why the in-binary fix
(A) matters.
Related: #3763 (same broken command, framed as cross-DB resolution — different fix; resolving it does
not stop this misroute) · #3699 (closed — town-level agents hook correctly; the self-sling path fix (A) uses) ·
#3839 / #4050 (closed —
gt slingself-targeting hardening; the machinery fix (A) relies on) · #3148 (Deaconboot patrol escalation flood) · #2386 (Deacon patrol fast-loop) · #3675, #4023 (deacon-patrol cluster).