Observe and debug a hank without guessing

A hank is a workflow made of codons (units of work). While a codon runs, the headless console (a run without an interactive display) can go quiet for minutes at a time, and silence tells you nothing about whether the run is healthy, stuck, or already finished. You do not have to guess. Every hankweave run leaves a trail of files in its execution directory: a runtime lock with a heartbeat, an event journal, a state file, agent logs, and a checkpoint repository. This page shows you how to read each of them, how to trace a failure from symptom to cause, and how to re-watch a finished run's recorded event stream.

Throughout the page, <exec> stands for the execution directory, and the commands assume you run them from inside it.

Watch a live run without smothering it#

Check a running hank about every five minutes rather than every second, and let a first run finish: first runs are learning runs, and stopping one early teaches you less than watching it out. The files to watch are the ones named by the runbook's observe step: events.jsonl, server.log, state.json, and the agentRoot/ outputs. Three checks cover most of what you need.

  1. Inspect .hankweave/runtime.lock. Its pid identifies the process and lastHeartbeat is rewritten every 30 seconds; the runtime treats a heartbeat older than 120 seconds as stale.
⌁ Terminal
cat .hankweave/runtime.lock
  1. Check process liveness separately. If you launched hankweave through the package bin (bunx hankweave@0.11.0 or npx hankweave@0.11.0), the bin entry maps hankweave to dist/index.js, so matching the launch command line with pgrep -f is a heuristic, not a guarantee; a direct node dist/index.js launch will not match this pattern:
⌁ Terminal
pgrep -f bin/hankweave
  1. For a clean finish, find one codon.completed journal event carrying cost for each terminal codon attempt (one per codon when nothing was retried or resumed). Then check server.log for Shutting down server: all codons completed, State transition: RunCompleted, and the exit-0 shutdown result.

Check-it: compare the journal with the captured completion events below, then confirm the corresponding server.log shutdown lines. A completion event is lifecycle evidence; inspect the expected artifact on disk before calling the run successful.

Captured completion events. This capture shows the full event sequence for a single-codon run: RunStarted, the codon's state transitions from preparing through running, the alternating cost and message-count updates while the agent works, the final cost and checkpoint, and RunCompleted. Use it as a reference shape when reading your own journal.

Output
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "RunStarted", "runId": "<id>", "transition": {"type": "RunStarted", "data": {"runId": "<id>", "runFolder": "~/.hankweave-executions/<exec-id>/.hankweave/runs/<id>", "gitBranch": "run-<id>", "startingConditions": {"type": "fresh"}, "serverPid": 2862}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonStarted", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonStarted", "data": {"runId": "<id>", "codonId": "summarize-notes"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "summarize-notes", "from": "preparing", "to": "starting", "metadata": {}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "summarize-notes", "from": "starting", "to": "initializing", "metadata": {"claudePid": 957441, "claudeLogPath": ".hankweave/runs/<id>/summarize-notes-claude.log"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "summarize-notes", "from": "initializing", "to": "running", "metadata": {"claudeSessionId": "cc67c965-b0af-4cef-8242-550f56ef618e"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "summarize-notes", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "summarize-notes", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonFinalCostSet", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonFinalCostSet", "data": {"runId": "<id>", "codonId": "summarize-notes", "finalCost":"<n>", "finalTokens": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CheckpointCreated", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CheckpointCreated", "data": {"runId": "<id>", "codonId": "summarize-notes", "checkpointType": "completed", "sha": "<sha>", "branch": "run-<id>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "summarize-notes", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "summarize-notes", "from": "running", "to": "completed", "metadata": {"exitCode": 0, "resultMessageReceived": true, "checkpointSha": "<sha>", "contextExceeded": false, "extensionCount": 0}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "codon.completed", "data": {"codonId": "summarize-notes", "success": true, "cost":"<n>", "duration":"<n>", "exitStatus": {"type": "success"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "RunCompleted", "runId": "<id>", "transition": {"type": "RunCompleted", "data": {"runId": "<id>"}}, "resultingState": {"currentRunId": null, "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}

A second completion capture. The same lifecycle for a different codon (normalize-aster), so you can see which fields stay constant and which vary between runs.

Output
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "RunStarted", "runId": "<id>", "transition": {"type": "RunStarted", "data": {"runId": "<id>", "runFolder": "~/.hankweave-executions/<exec-id>/.hankweave/runs/<id>", "gitBranch": "run-<id>", "startingConditions": {"type": "fresh"}, "serverPid": 4026}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonStarted", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonStarted", "data": {"runId": "<id>", "codonId": "normalize-aster"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "normalize-aster", "from": "preparing", "to": "starting", "metadata": {}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "normalize-aster", "from": "starting", "to": "initializing", "metadata": {"claudePid": 978393, "claudeLogPath": ".hankweave/runs/<id>/normalize-aster-claude.log"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "normalize-aster", "from": "initializing", "to": "running", "metadata": {"claudeSessionId": "40cbc6c5-0f8f-4fc3-a540-e435f5bb05a4"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CostsIncremented", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CostsIncremented", "data": {"runId": "<id>", "codonId": "normalize-aster", "costDelta":"<n>", "tokensDelta": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "AssistantMessageCountUpdated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "AssistantMessageCountUpdated", "data": {"runId": "<id>", "codonId": "normalize-aster", "newCount":"<n>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonFinalCostSet", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonFinalCostSet", "data": {"runId": "<id>", "codonId": "normalize-aster", "finalCost":"<n>", "finalTokens": {"inputTokens":"<n>", "outputTokens":"<n>", "cacheCreationTokens":"<n>", "cacheReadTokens":"<n>"}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CheckpointCreated", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CheckpointCreated", "data": {"runId": "<id>", "codonId": "normalize-aster", "checkpointType": "completed", "sha": "<sha>", "branch": "run-<id>"}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "CodonTransitioned", "runId": "<id>", "codonId": "normalize-aster", "transition": {"type": "CodonTransitioned", "data": {"runId": "<id>", "codonId": "normalize-aster", "from": "running", "to": "completed", "metadata": {"exitCode": 0, "resultMessageReceived": true, "checkpointSha": "<sha>", "contextExceeded": false, "extensionCount": 0}}}, "resultingState": {"currentRunId": "<id>", "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "codon.completed", "data": {"codonId": "normalize-aster", "success": true, "cost":"<n>", "duration":"<n>", "exitStatus": {"type": "success"}}}
{"id": "<id>", "timestamp": "<ts>", "type": "state.transition", "data": {"transitionType": "RunCompleted", "runId": "<id>", "transition": {"type": "RunCompleted", "data": {"runId": "<id>"}}, "resultingState": {"currentRunId": null, "runCount": 1, "totalCost":"<n>", "currentRunCost":"<n>"}}}

Find why a codon failed in four stops#

When a codon fails, the evidence is spread across four places. From <exec>, follow the failure through them in order:

  1. Open .hankweave/state.json, not a file at the execution root. Hankweave writes state.json.bak before each save.
  2. Read the failed codon's exact claudeLogPath; never infer an agent-log filename.
  3. Inspect the event journal. While a run is live, the journal is .hankweave/events/events.jsonl. A finished run may have left it dieted to events.jsonl.zst plus journal.meta.json, so restore it before inspecting with bunx hankweave@0.11.0 --restore-journal <exec>, or read it through the runtime by resuming the execution.
  4. Compare the sealed checkpoint (saved file state) with the checkpoint repository.

The state file is where the failure is recorded. A failed codon record contains failedDuring, exitCode, failureReason, partialCost, partialTokens, and, optionally, errorCheckpoint. The complete state shape is documented in reference/state-file. The stored failure-reason shape is shown here; use reference/errors-and-exit-codes for its first-match-wins classification taxonomy.

TS
// Failure reason schema
const failureReasonSchema = z.object({
  type: z.enum(["timeout", "rate-limit", "api-error", "sentinel-load-failure", "unknown"]),
  retriable: z.boolean(),
  message: z.string().optional(),
  sentinelRefs: z.array(z.string()).optional(), // Which sentinels failed (for sentinel-load-failure)
  /**
   * How long the provider asked us to wait before retrying, in milliseconds,
   * when its error carried an explicit hint (a Retry-After header value, a
   * "retry after 30s" phrasing, etc). Parsed by classifyApiErrorText and
   * consumed by computeRetryDelayMs, which prefers it over computed backoff —
   * the provider knows its own limit window better than we can guess.
   */
  retryAfterMs: z.number().optional(),
});

Read the codon's exit code without treating it as the whole diagnosis. The common values:

Scroll to explore the table →
CodeMeaning
1General error
-1Killed by a signal
130Ctrl+C

That table reproduces the stale comment in state-types.ts and does not match the code. A failed codon's recorded exitCode is 0 or 1 from the harness, or -1 written by the runtime for a force stop, a run crash, or a failure before any runner ran (such as a sentinel load failure), so -1 does not mean a signal kill; nothing records 130, and after a graceful Ctrl+C the interrupted codon stays running with a null exit code.

New runs are at .runs[0] because the runtime unshifts them. To avoid relying on insertion order, sort by startTime and take the last run. These commands select the failed codon and total spend, then show the order-safe run selection:

⌁ Terminal
jq '.runs[0].codons[] | select(.status=="failed") | {codonId, failedDuring, failureReason, exitCode}' .hankweave/state.json
jq '[.runs[0].codons[] | (.finalCost // .partialCost // 0)] | add' .hankweave/state.json
jq '(.runs | sort_by(.startTime) | last)' .hankweave/state.json

A kill is different from a self-failing codon. Immediately after a SIGTERM, the run's snapshot shows "status": "failed" (the shutdown fires RunFailed) with the interrupted codon still "status": "running" and "exitCode": null – the kill does not mark the codon failed in place. On resume, the original run stays failed with that codon running/null, and the resume starts a new run. A codon that actually failed records "status": "failed" with failedDuring and a failureReason, as in the capture below.

The running example for the rest of this page. A two-codon hank whose second codon (write-second) fails on an API error, captured 2026-09-27 against hankweave@0.11.0 with onFailure at its default abort. The transcript ends exit=1:

Output

╭────────────────────────────────────────────────────────────────────╮
│  Hankweave v0.11.0                                                 │
│  linux x64 • node v22.23.2                                         │
╰────────────────────────────────────────────────────────────────────╯

Created new execution directory: ~/.hankweave-executions/<exec-id>
New execution: <exec-id>
  Source → data
  Exec   → ~/.hankweave-executions/<exec-id>
  SDKs   → Claude node_modules ✓

╭──────────────────────────────────────────────────────────────────────────────╮
│  Failed codon v1.0.0                                                         │
│  2 codons • 0 loops                                                          │
╰──────────────────────────────────────────────────────────────────────────────╯

├─ [1] write-line (Write one line)
│     model: haiku │ mode: fresh │ prompts: 1 (2 lines)
│     checkpointedGlobs: 1
│     ↓
└─ [2] write-second (Write a second line)
      model: gemini-2.5-flash (Gemini 2.5 Flash) │ mode: fresh
      prompts: 1 (2 lines) │ checkpointedGlobs: 1
      rigs: copy: <workspace>...


══════════════════════════════════════════════════
  Hankweave Server Started
  WebSocket: ws://localhost:<port>
══════════════════════════════════════════════════

Running in headless mode on port <port>
➜ Listening on: http://localhost:<port>/ (all interfaces)
[<ts>] [ERROR] Pi session setup failed: Missing API key for provider 'google'. Set GEMINI_API_KEY.
[<ts>] [ERROR] Codon write-second failure classified: API auth error: Missing API key for provider 'google'. Set GEMINI_API_KEY.
exit=1

The failed codon's state record carries everything the four stops need – failedDuring, the stored failureReason, claudeLogPath, exitCode, and an errorCheckpoint:

Output
[
  {
    "codonId": "write-second",
    "startTime": "<ts>",
    "status": "failed",
    "endTime": "<ts>",
    "failedDuring": "initializing",
    "exitCode": 1,
    "failureReason": {
      "type": "api-error",
      "retriable": false,
      "message": "API auth error: Missing API key for provider 'google'. Set GEMINI_API_KEY."
    },
    "partialCost": 0,
    "partialTokens": {
      "inputTokens": "<n>",
      "outputTokens": "<n>",
      "cacheCreationTokens": "<n>",
      "cacheReadTokens": "<n>"
    },
    "rigSetupCheckpoint": "<sha>",
    "claudePid": "<pid>",
    "claudeLogPath": ".hankweave/runs/<id>/write-second-claude.log",
    "errorCheckpoint": "<sha>"
  }
]

Read the agent log without guessing its name#

Step 2 above sends you to the agent log; here is how to open the right one. Resolve the codon's claudeLogPath relative to <exec>. A stored path can look like .hankweave/runs/<runId>/<codonId>-claude.log. The run directory is created at run start, and <runId> has the form {unixMs}-{5-char random}; it has no run- prefix. That prefix belongs to checkpoint branches.

For both harnesses, replace # with - in an in-loop codon ID and use the -claude.log suffix. Thus normalize-next#0 maps to normalize-next-0-claude.log, but the value in state remains authoritative.

Read the file as JSONL (one JSON object per line) and use its last result line for the outcome. If a session dies before the SDK emits a result (an idle timeout, for example), the runtime appends a synthetic result with type:"result", subtype:"error", and is_error:true so the failure stays classifiable. An SDK timeout marker is different: a synthetic assistant message has message.model:"<synthetic>" and content API Error: Request timed out.; the runtime's timeout and context checks key on that model marker, which is not a real model.

In the running example, the claudeLogPath stored in the state record resolves to the captured agent log under .hankweave/runs/<runId>/write-second-claude.log, whose SDK result line carries the error.

Check-it: the claudeLogPath you read from state resolves to an existing file under .hankweave/runs/<runId>/.

Diff the changes the run actually made#

The state file tells you what the run reported; the checkpoint store tells you what it wrote. To compare what changed, select the checkpoint store explicitly rather than the main project's .git. The store is a non-bare repository with git directory <exec>/.hankweave/checkpoints/.hankweavecheckpoints and work tree agentRoot/; a plain git log from the execution directory silently answers from a parent repo instead. See Checkpoints for the mechanics. From the execution directory:

⌁ Terminal
git --git-dir=.hankweave/checkpoints/.hankweavecheckpoints log --oneline
git --git-dir=.hankweave/checkpoints/.hankweavecheckpoints log --format='%h %an <%ae> %s'
git --git-dir=.hankweave/checkpoints/.hankweavecheckpoints branch -a

Checkpoint subjects begin {status}:{codonId} [run:{runId}] {codonName}. The stored checkpoint types are rig-setup (the rig – the workspace setup stage that runs before the agent session), completed, error, and skipped; a graceful shutdown with a codon still in progress writes an exit: commit prefix whose stored type is skipped, while a force shutdown, a second signal, a crash or a kill writes no exit checkpoint. The commit body carries Codon, Status, Timestamp, and Duration, and checkpoint commits are authored by froggie@southbridge.ai.

Only files matching the configured checkpointedFiles globs (file-pattern globs), after gitignore resolution, are committed from agentRoot/. A file outside those globs is invisible to rollback.

To compare two runs' file states, replace run-A and run-B below with two run-<runId> branch names from the branch -a listing above:

⌁ Terminal
git --git-dir=.hankweave/checkpoints/.hankweavecheckpoints diff run-A run-B

In the running example, the checkpoint log shows the three commit kinds a failed codon leaves behind – the rig setup, the first codon's completion, and the failure:

Output
<sha7> error:write-second [run:<id>] Write a second line
<sha7> rig-setup:write-second [run:<id>] Write a second line
<sha7> completed:write-line [run:<id>] Write one line
<sha7> Initial checkpoint setup

Check-it: the --format log command above shows rig-setup:, completed: and error: commits by Hankweave Runtime <froggie@southbridge.ai>.

[removed 0.11.0] A resume-after-failure rollback once reported Target checkpoint … not found in manifest and deleted completed files; the outputs were recovered from the error:package-gate checkpoint, so the shadow git held failed-codon state too. That error is gone at 0.11.0.

Classify failures by their phase#

Once you have the state record and the log, the fastest way to narrow the cause is failedDuring: preparing, starting, initializing, running, or completing-sentinels. Each phase points at a different suspect.

  • preparing: diagnose rig setup. A rig is the workspace setup stage, and it runs while the codon is preparing; the codon moves to starting only after rig setup completes. A rig failure surfaces as a rig.setup.failed event carrying failureType, optional exitCode and commandIndex, and ignored. If a command rig needs bun rigs/preflight.ts, deliver it with a copy rig first; otherwise the command rig can report ENOENT.
  • starting or initializing: inspect harness launch and self-test failures. A missing key is self-test class check, not auth: the failed check is named authentication, and its message is the real No authentication found (set ANTHROPIC_API_KEY). The full class enum is owned by reference/errors-and-exit-codes.

Captured startup failure. This capture shows the self-test failing before any codon runs: the authentication check reports the missing ANTHROPIC_API_KEY, the server refuses to start, and the process exits 1.

Output

╭────────────────────────────────────────────────────────────────────╮
│  Hankweave v0.11.0                                                 │
│  linux x64 • node v22.23.2                                         │
╰────────────────────────────────────────────────────────────────────╯

Created new execution directory: ~/.hankweave-executions/<exec-id>
New execution: <exec-id>
  Source → data
  Exec   → ~/.hankweave-executions/<exec-id>
  SDKs   → Claude node_modules ✓

[<ts>] [ERROR] Self-test completed: FAILED
[<ts>] [ERROR] Self-test FAILED: Some checks failed
[<ts>] [ERROR]   - authentication: ✗ No authentication found (set ANTHROPIC_API_KEY)
[ERROR] Server startup failed!
Error message: Self-test failed for 1 model(s):
  - Claude Haiku 4.5 (latest) (anthropic/claude-haiku-4-5): Some checks failed
      • authentication: No authentication found (set ANTHROPIC_API_KEY)
Stack trace:
Error: Self-test failed for 1 model(s):
  - Claude Haiku 4.5 (latest) (anthropic/claude-haiku-4-5): Some checks failed
      • authentication: No authentication found (set ANTHROPIC_API_KEY)
    at c$ (file://<workspace>/build/runtime/0.11.0/node_modules/hankweave/dist/index.js:337:5382)
    at async N1t (file://<workspace>/build/runtime/0.11.0/node_modules/hankweave/dist/index.js:873:567)
    at async file://<workspace>/build/runtime/0.11.0/node_modules/hankweave/dist/index.js:874:658

exit=1
  • running: treat network, timeout, 429, and 5xx/overloaded failures as retriable; auth, billing/credit, and quota failures are permanent. A lived 429 with code 1308 was classified as non-retriable, so inspect the stored classification before retrying. A budget configuration without maxDollars or maxTimeSeconds leaves that failure unanticipated. With compaction (context trimming) off by default, a plain codon can hit the context boundary; inspect the log for a Prompt is too long-class error or an output-token-maximum synthetic message.
  • completing-sentinels: this value is written only when a force stop or a crash reads the live status during the sentinel drain. A checkpoint-creation failure records the earlier status, normally running – do not read completing-sentinels as "checkpoint creation failed".

The three events below are the ones you will meet most often when classifying by phase: the codon completion record, the rig failure record, and the sentinel unload record.

Scroll to explore the table →
idcategoryjournaledsentinelRoutedpayloadFieldsreceipts
codon.completedserver-statetruetruecodonId, success, cost, duration, exitStatus, failureReason?, failureIgnored?, budgetExceeded?schemas/event-schemas.ts:672, schemas/event-schemas.ts:1075, schemas/event-schemas.ts:1349, hankweave-runtime.ts:3792
rig.setup.failedagentic-backbonetruetruecodonId, failureType, exitCode?, commandIndex?, ignoredschemas/event-schemas.ts:714, schemas/event-schemas.ts:1105, schemas/event-schemas.ts:1357, hankweave-runtime.ts:2213
sentinel.unloadedsentineltruefalsesentinelId, codonId, reason, errorType?, finalCost, llmCallCountschemas/event-schemas.ts:816, schemas/event-schemas.ts:1114, schemas/event-schemas.ts:1377, sentinels/sentinel-manager.ts:908

When a sentinel unloads, inspect its reason, finalCost, and llmCallCount. consecutive-failures means the sentinel's own LLM calls kept failing – it is reached only by regular errors of a non-conversational immediate sentinel (a debounce, count or timeWindow strategy, or a corruption error, never trips the threshold) – so check the sentinel's key and quota before checking the hank. The sentinel manager can also report No LLM provider available while codons resolve the same key fine; treat that as a sentinel-side key-resolution symptom. A required sentinel (failCodonIfNotLoaded: true) fails the codon only when its config fails to load (a missing, unparseable or schema-invalid ref, or a sentinel-manager instantiation throw), while it is still starting, with failureReason type sentinel-load-failure, retriable:false, and sentinelRefs naming the sentinels; a sentinel skipped for an unregistered model, an unconfigured or unhealthy provider, or a per-sentinel instantiation error only logs, and the codon runs without it.

[changed 0.8.0] Provider retry classification was overhauled and compaction became off by default; pre-0.8.0 failure-reading habits mislead.

Signals that look conclusive and are not#

Some signals look conclusive and are not. Before you trust any of them, check these known cases.

A success signal can be incomplete. An observed codon ended in success after about 15,000 output tokens of thinking, wrote no file, and sealed an empty checkpoint. Verify the artifact on disk rather than trusting the success event.

Budget-cap behavior belongs to the runbook and Budgets. When a cap trips, the runtime requests the codon's interruption, and onExceeded decides the recorded outcome: complete (the default) marks the codon completed with what it produced, fail marks it failed and hands it to onFailure. The outcome is settled before the attempt's codon.completed, so the event already carries the trip's result. A fail trip forfeits the spend with no delivered output. The two zai highspeed models glm-5.2-highspeed and glm-5.3-highspeed report cost 0, so no dollar cap can stop them – use time or token caps for those. The line aborted by user can describe a budget abort rather than a person stopping the run.

An exhaustWithPrompt extension round runs inside one codon lifecycle, and no checkpoint is written between rounds. If the final round fails, the codon is recorded failed with an error checkpoint, and onFailure decides whether the run ends – so a resume or retry runs that codon again, and files it wrote may remain in the workspace.

Keep process exit status separate from the codon's state exitCode. The shutdown reason decides the process exit code: 0 for all codons completed when the run did not fail, and for the exact reasons SIGINT, SIGTERM and client request; every other reason, including a server.shutdown carrying its own data.reason and the TUI's user request quit, exits 1 and marks the run RunFailed. forceShutdown (a second SIGINT/SIGTERM or a server.force_shutdown request) aborts the running harness session – for the Claude Agent SDK, an abort that ends its agent subprocess with SIGTERM; for Pi, an in-process abort – skips checkpoints, telemetry and state transitions, and exits 1. The double-Ctrl+C capture ends exit=1 for exactly this reason, so process exit 0 alone does not prove clean completion.

A first-party DeepSeek V4 Pro codon can reason silently for more than 15 minutes without streamed agent events – a hang-looking signal that is actually thinking. On a long judgment codon, the captured anchor run passed --shim-idle-timeout 1800.

Re-watch a completed run with --replay#

Sometimes the question is not why a run failed but what it did, and you want to see the event stream again. Replay covers that case: use it to re-watch a failed run's event sequence without spending tokens on new codon calls, to develop a TUI, client or tooling against real event streams, or to demo a run. Replay copies the execution directory to a temporary location (the original is preserved; the copy is deleted on exit), removes the copied runtime lock, discovers the hank and data from execution-meta.json, and paces output from recorded timestamps. It starts a fresh replay run and skips rigs, sentinels, and the per-model self-tests and catalog preflight (a normal startup and --validate run those). Its codons re-emit recorded output, so a replay makes no new codon model calls – but it does not skip the runtime's startup provider health checks, which can make billable calls when provider credentials are set, and it does not test changed rigs or external side effects.

Run it from inside the execution directory you want to replay:

⌁ Terminal
bunx hankweave@0.11.0 --replay .

--replay cannot be combined with --execution, and it is listed in --help. The captured completed-execution replay shows the [REPLAY] metadata reuse, the temporary hankweave-replay-… copy, a fresh run, and exit 0.

Replay capture

Output

╭────────────────────────────────────────────────────────────────────╮
│  Hankweave v0.11.0                                                 │
│  linux x64 • node v22.23.2                                         │
╰────────────────────────────────────────────────────────────────────╯

[REPLAY] Using hank config from execution metadata: <workspace>/fixtures/scenarios/silent-reuse/hank.json
[REPLAY] Using data source from execution metadata: <workspace>/fixtures/scenarios/silent-reuse/data
[REPLAY] Copied execution dir to <capture-attempt><tmp>/hankweave-replay-1790499103051-okkd
Removed a stale diet pair found beside the live events.jsonl (verified duplicate of the journal; interrupted prune or diet)
Resuming: hankweave-replay-1790499103051-okkd
  Source → data
  Exec   → <capture-attempt><tmp>/hankweave-replay-1790499103051-okkd
  SDKs   → Claude node_modules ✓

╭──────────────────────────────────────────────────────────────────────────────╮
│  Silent reuse v1.0.0                                                         │
│  1 codon • 0 loops                                                           │
╰──────────────────────────────────────────────────────────────────────────────╯

└─ [1] write-line (Write one line)
      model: haiku │ mode: fresh │ prompts: 1 (2 lines)
      checkpointedGlobs: 1


══════════════════════════════════════════════════
  Hankweave Server Started
  WebSocket: ws://localhost:<port>
══════════════════════════════════════════════════

Running in headless mode on port <port>
➜ Listening on: http://localhost:<port>/ (all interfaces)

exit=0

Check-it: the replay copies the execution to a temp dir, discovers hank and data from execution metadata, and re-runs with no new codon model calls (rigs, sentinels, and the per-model self-tests and catalog preflight skipped; the startup provider health checks can still call when credentials are set).

Avoid patterns that make debugging harder#

The techniques above work best when the response to a failure is deliberate. Before changing the hank, make the uncertainty explicit, and steer around these habits:

  • Infinite retry: read failureReason and its retriable classification before retrying an unchanged failure.
  • Prompt patching: identify why output went wrong before adding instructions.
  • Verification blindness: check the artifact on disk instead of accepting a success signal.
  • Debugging by deletion: do not remove codons to make a complex hank appear to work. --start-at and --end-at are not hankweave flags; use codon isolation, the extraction technique owned by testing and hardening.
  • Kitchen-sink prompt: do not bury needed context in an everything-at-once prompt.
  • Ignoring partial progress: keep a useful checkpoint while repairing a later failure.
  • Monolithic codon: split stages when you need to locate failures between them.

Once we have found the cause, use Resume, rollback, and retry for recovery procedures. For failure classes and exit contracts, see Errors and exit codes; for the state schema, see State file.