Debugging a run
SkillMonitoring & opsLets your agent debug failed or stuck Weft runs by reading their logs, events, and skip reasons in order.
Then ask your AI: use the Debugging a run skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add it as a step in a pipeline you build on Ahel, or connect Ahel to Claude, ChatGPT, Cursor, Claude Code or Codex and ask your AI to use it.
About this skill
Read when a run failed, did the wrong thing, never started, got stuck or was cancelled, or when you need to read what a past run did: the order to read a run in (executions, logs, events), what each skip and cancel reason means, and the debugging playbook, from a compile error to a daemon that does
What this skill tells your AI
The instructions your AI receives, as published by weavemindai/weft in tangle/claude-code/.claude/skills/weft-debugging/SKILL.md and read by Ahel’s review.
Reading a run
Each verb below prints one compact line per run or per event and has a flag that opens the part you want, so a forty-node run reads without loading the whole journal. You read in this order and stop when you hold the failing node and the wrong value.
- If you want to know what ran, or whether your trigger fired since the
change:
weft executions --limit 10 --phase fire. One line per run: execution id, status (running,waiting_for_input,completed,failed,cancelled), phase, the start time in UTC, the project id, the entry node (the trigger that fired), the tags. An activate, a resync or an infra start creates setup runs, phasestrigger_setupandinfra_setup;--phase firehides them.--project <id>narrows to one project. A run whose trigger hasrecorded: falseis listed only if it failed (with the step that failed and why, nothing before it) or reported a cost or asked weft for something on its behalf, so a route that answered and left nothing here may simply be unrecorded:weft statusshows how many runs each trigger started in the last minute or two and how many failed, which is how you confirm its runs happen. - If a run failed and you want the reason:
weft logs <execution-id>. It prints what the run's nodes wrote and every failure the journal recorded, aserrorandwarnlines. A line about one node names it (and the loop iteration,llm#3:); a line about the run itself (the run failing, a cancel) names none:[2026-09-02 21:36:47] error llm: node failed: the service answered 401 .... It is the last 1000 lines and says so when the run wrote more;--limitraises that up to 20000, higher is refused. For most failures this is enough and you never open the events.(no logs: ...)means the run wrote nothing and recorded no failure: it did not fail, so you check its status. - If you want the values on the wires:
weft events <execution-id>. One line per event: UTC time, kind, node, then everything the row carries askey=value, each cut to a screen's width (input=onnode_started,output=onnode_completed,error=onnode_failed,reason=on a skip or a cancel,token=on a suspension, and so on). You narrow before you read:--kind failedfor the failures (a substring matches, so this catchesnode_failedandexecution_failed),--kind node_skippedfor what did not run and why,--node <id>for every event on one node,--iteration 3for the fourth time round a loop (counted from 0;3.0is the first iteration of a loop inside it, outermost first). - If a value on one of those lines is cut off: it was cut to fit the
screen, not stored short.
weft events <execution-id> --node <id> --fullprints it whole, and--jsongives you the replay rows forgreporjq. No value a run recorded is unreadable, so you never report one as unreadable. - If a node did not run: its
node_skippedline carries the reason.did_not_flow: the node's_should_flowsaid no.flow_closed: nothing ever answered its_should_flow, so walk to whatever drives that wire.required_input_closednames the input that arrived closed, so walk to that node's line.every_input_closedandone_of_group_closedare the same story for a node with no required inputs and for a@require_one_ofgroup.scope_skippednames the group or loop whose_should_flowsaid no, taking this node with it: walk to that container. (A group input arriving closed is not this: it passes through to the nodes inside that read it, and they carry their own reason.) - If a
Debugshowsoutput=empty: correct, aDebughas no outputs. Its value is on itsnode_startedline asinput=, orweft events <execution-id> --node <debug id>. - If a past run shows no values at all: a run's rows say what happened,
not what it meant; the editor works out every input and output by
replaying those rows against the code the run ran, so a run whose code
[the daemon] no longer holds shows every node with no values, and the run
itself says why. The empty graph is not "the node produced nothing" and
not a viewer bug. The code is kept as long as any run points at it, so
this is rare. If the files are still on disk,
weft buildputs the program back under the hash the run names and the run reads as it did. If not, the run is readable only as its shape, andweft clean <execution-id>removes it. - In VS Code with the weft extension: the Executions view, "View in Graph"
replays the run in the graph, values on every wire;
Debugnodes render their latest value inline. For the full editor surface (inspector, action bar, targets, everything clickable), go and read theweft-editorskill.
The debugging playbook
- It does not compile. Read the diagnostics:
line:column message, a stable slug, and the message names the fix. You fix what it names; you never route around a diagnostic.weft validate --file src/main.weft < src/main.weftre-checks without building. A diagnostic naming the catalog (an enrichment error, an unknown field, "a stale base_catalog copy") means the stdlib copy lags the installed weft:weft catalog update, then re-check, before anything else. - A run failed.
weft logs <execution-id>names the node and the error. If that is not enough,weft events <execution-id> --node <that node>shows the values that reached it, on itsnode_startedline. For the compiler error slugs, go and read theweft-languageskill. A node's own error text is written by the node's author. A node error saying it has no connection picked is [the runtime tier] firing at execution, not a source bug: you pick a stored connection yourself (weft connect --node <id> --grant <grant>) or send the user to the node's Connect button /weft connectin their terminal, then run again. A failure sayingthe worker running '<node>' went away while it was runningmeans the worker of a durable run died mid-step: weft does not run a step again once its start is on record, because the step may have partly happened. Check what it did outside (the email, the row, the post), then, if it has to happen again, start a run at that step by hand:weft run --from <node>=<inputs>, with the inputs fromweft events <execution-id> --node <node> --full. If the message sayswent away while it was reading a streaminstead, the items it read are gone from the stream, so--fromcannot hand them back: check what it did, then run the whole execution again. - A value is wrong, not failed. Work upstream from the output: open the run, look at the top-level groups, find the first whose output is already wrong, descend, repeat. When you hold the concrete failing case, iterate on that one step (build, run, look) and feed the earlier stages' passing examples through again.
- Suspended. Expected for human-in-the-loop: nothing is wrong. A
suspended run is alive and costs nothing: the
HumanQueryparks, the worker exits, and the answer resumes it. The person answers in the browser extension (built with./setup.sh --browser --no-sign). If nobody should have been asked, the bug is the_should_flowthat routed into the person. - Cancelled. Read the reason on
execution_cancelled.Cancelled by useris a person: the Stop button,weft stop, a project deactivate or wipe, or a cancel through a signal token.Stopped by execution <execution-id> (tag <tag>)means another run asked the engine to stop everything carrying that tag: look at that run, and if no sibling was supposed to stop this one, the bug is in whichever node tagged and stopped it (weft executionsshows each run's tags, so you see which runs shared it).Caller disconnectedmeans the live caller this run was answering dropped its connection.the worker running this run went away; a fast run lives in its worker's memory, so it is not run againmeans a fast run lost its worker (a crash, a machine gone): check what it did outside, then start it again (let the trigger fire again, or fire it by hand:weft bake, thenweft run --fire <trigger>=<payload>, with thepayloadon the trigger'snode_kickedline inweft events <execution-id>), and if the trigger's runs have to carry on after a worker dies, setdurable: trueon it. A durable run whose worker stops or dies is never cancelled: another worker picks it up from its record, and only the step that was running fails (item 2).the run had to leave the copy of the program running it (...), and it cannot be suspended: <why>means its worker was stopping and this run could not move to another one; the<why>names the reason: its trigger hasrecorded: false, its route does not haveoutlivesCaller, or a bus between its nodes was open. Check what it did outside, then start it again the same way as a fast run that lost its worker. Anything else is the runtime's own reason, printed as words. - Stuck. The engine proved nothing can proceed; that is a graph-shape
bug (a wire the compiler could not catch). The
execution_failedline names every firing left holding a pulse and the wired ports it never received (theirs has value, still waiting on go): walk to whatever should have driven the missing port. - Nothing ran. Nothing reached the branch: check that the trigger that
should have fired is activated, then walk from it down to the first
_should_flowor required input that closed. - Too much is running. The one failure nobody reports, because nothing
errors: runs accumulate and the only symptom is a list that keeps
growing. Every live connection is its own run for as long as the caller
holds it, so a page anyone can open, or a client that reconnects by
itself, can leave one behind per visit.
weft executionsis the check, and what you look at is the STANDING count of runs still going, not whether the one you started finished. Drive the thing, close it, look again: the count comes back down, or you have found a leak. Several runs of one trigger that never end is a finding you report, with the count and how long they have stood. - A timer or a wake that never arrived. A wake the receiver refuses
for good is dropped once, with one error in
weft daemon logsnaming it, the status and the answer (on a local install: any4xxexcept408and429; the listener answers a wake it cannot read200withdroppedand logs it). A failure that may pass (a5xx,408,429, no answer) is sent again later, waiting longer each time. So a wait that never woke is read inweft daemon logs. - [the daemon] unreachable (connection refused, an editor button that
does nothing). You read, never restart:
weft daemon statusandweft daemon logs --tail 50say what [the daemon] is doing. Then you tell the user the symptom (the exact refusal, and what you were running) and stop; bringing [the daemon] back is their install's job. If you catch yourself typingweft daemon start,stoporrestart, stop and write: "Wait. The daemon is not mine to restart." Then report the symptom.
Signals
- GitHub stars
- 2k
- Forks
- 218
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
weft-debugging- Source
- github.com/weavemindai/weft
More in Monitoring & ops
Skill · anthropics
More in Monitoring & opspricing
Skill · coreyhaines31
More in Monitoring & opsagent-eval
Skill · affaan-m
More in Monitoring & opsclaude-devfleet
Skill · affaan-m
More in Monitoring & opsbabysit
Skill · thedotmack
More in Monitoring & opseng-runbook
Skill · nexu-io
More in Monitoring & ops