Skip to content

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

Description

@deltaag

Bug Description

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

  1. 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).
  2. The Deacon follows the prompt → gt sling mol-deacon-patrol deaconError: deferred dispatch requires a rig target.
  3. 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>).
  4. 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.
  5. Repeats every cycle — a fresh throwaway polecat per Deacon patrol cycle.

Expected Behavior

On cold-start the Deacon runs mol-deacon-patrol in 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 maininternal/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.

Suggested fix (in order):

  1. (A) Fix the boot prompt — minimal, uses existing code. Replace both embedded variants of
    gt sling mol-deacon-patrol deacon with the no-target self-sling gt sling mol-deacon-patrol
    (or gt sling mol-deacon-patrol .). gt sling's empty/dot-target path already routes through
    resolveSelfTarget()deacon/, hooking the patrol onto the Deacon with no rig spawn — exactly the
    intended 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.
  2. (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.)
  3. (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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions