#4870·gastown

Hook durability silently broken: gt patrol report / gt hook leave agent hook_bead null, causing an orphaned patrol wisp every cycle

Author: BBerabiCreated Sep 15, 2026Updated Sep 15, 2026
Labelsstatus/needs-triage

Summary

Hook assignment does not persist to the agent bead. gt patrol report creates a new patrol wisp that reports as HOOKED on the wisp side, but the agent bead's hook_bead stays null. A manual gt hook <wisp> does not fix it either — the null survives.

Because gt prime's startup logic branches on the agent's hook being empty, every handoff creates a duplicate patrol wisp and silently orphans the previous one. Combined with #4869 (compaction is a no-op), orphans accumulate without bound.

Observed

$ bd show hq-wisp-tcct1f
◇ hq-wisp-tcct1f · mol-deacon-patrol   [● P2 · HOOKED]
Assignee: deacon/ · Type: molecule

$ bd show hq-deacon
  agent_state: idle
  hook_bead: null

The wisp believes it is hooked; the agent has no pointer to it. This was observed immediately after gt patrol report, then again after an explicit gt hook hq-wisp-tcct1f, then again after gt handoff. The manual fix never persisted.

Likely root cause — existing report

We have a three-week-old internal report (2026-08-24) with a concrete root-cause candidate: a query path inside the gt binary references a depends_on_id column that does not exist. The live schema has depends_on_issue_id / depends_on_wisp_id / depends_on_external. The Dolt server was logging, at 1-2s intervals:

error="column \"depends_on_id\" could not be found in any table in scope" connectionDb=hq

and the error was being swallowed, returning an empty/not-found result rather than surfacing. That reporter saw the same shape from the other direction: gt patrol new returned a bead ID that was immediately unreadable via bd show, with gt hook reporting 'Nothing on hook' straight after.

Caveat: I could not reproduce that exact log line in a fresh gt dolt dump today, so either it has been partially addressed or it only fires on specific hook-resolution calls. Either way the hook-persistence failure is still reproducible as shown above.

Why this matters more than it looks

Hook durability is the primitive that makes work survive session restarts. When it silently no-ops:

  1. Each patrol cycle orphans its predecessor's wisp (HOOKED, assigned, never closed).
  2. With #4869 preventing reclamation, those accumulate — we breached an 11,491-wisp alert threshold against a limit of 800.
  3. That breach then gets triaged as its own incident, and reaper agents are dispatched repeatedly to manually clear a backlog that should never have formed. We have five separate escalations of that shape.

So a single swallowed SQL error has generated four downstream incident classes, each of which looked independent.

Separate finding from the same diagnostic

gt dolt dump currently shows a high-frequency error storm unrelated to the above — many per second:

level=error msg="unable to process ComInitDB: database not found: gt" database=gt
level=error msg="failed to set the database client ...: database not found: mo (errno 1049)"

Something is repeatedly opening connections against database names gt and mo, neither of which exists on the server. Possibly benign retry noise, but it is constant and worth a look while someone is in this code.

Suggested direction

Audit every query path in hook/patrol resolution for the renamed dependencies columns, and — more importantly — stop swallowing the error. A failed hook write returning success is what made this invisible for three weeks. Surfacing the SQL error at the call site would have turned this into a one-day bug.

Environment: gt v1.2.1 dev@319d33a91b2d, bd v1.2.2 dev, Dolt server pid 2448343, town at ~/gt.