Skip to content

Troubleshooting

Most failures are refusals, not crashes: threadle preflights a run and stops before spending money. The message names the node.

ProblemCauseFix
No sessions listed for a providerIts storage directory does not exist where threadle looksInstall the tool, or point threadle at the right root with CLAUDE_CONFIG_DIR / OPENCODE_DATA_DIR / CURSOR_CONFIG_DIR
detached run blocked — fix (or mute) node(s) first: …One or more readiness blockersFix the named nodes, or mute them
model not set — pick one in the inspectorAn agent node has no modelPick a model in the inspector — detached runs cannot ask
required input not wiredA node that needs an input has noneWire it, or mute it. ⌀ Loose ends lists them all
context not materializedA context node has no payload hashMaterialize it in the Library first, or mute the node
this workflow has N interactive gate(s) … — pass approveAllApproval or live-handoff nodes in a server / detached runAdd --approve-all, use and confirm, or run in-tab with for splice
threadle server not reachablestatus / logs / run --detach with no serverStart threadle --no-open first
custom node "<name>" is not installedThe graph references a node folder that isn’t on this machineImport or create the node under ~/.config/threadle/nodes/
nothing to run for "<target>"Not a template id, file path, or graph idthreadle --list-templates, or check the path
cycle detected — workflow can never finishThe graph has a loopBreak the cycle — Loose ends flags unreachable nodes
EADDRINUSE at startupPort 4570 already takenthreadle --port 4600
403 forbidden host / cross-origin write rejectedReaching the API from a non-loopback hostnameUse 127.0.0.1, localhost, or [::1]
Features silently not workingBrowser tab and server are different buildsFollow the banner — see below
source: unavailable (offline and no local cache)No local price table and no networkSee below

Availability is checked per provider and nothing cascades — a missing provider contributes no sessions and breaks nothing else.

ProviderReported available when
Claude Code~/.claude/projects exists
opencode~/.local/share/opencode/opencode.db exists and can be read
Cursor~/.cursor/projects exists, or agent --version succeeds

Storage and CLI are separate signals. Claude Code and opencode report available on storage alone, so you can browse history on a machine where the CLI is gone — a run will then fail at spawn time instead.

If you use CLAUDE_CONFIG_DIR to keep Claude Code’s state somewhere else, threadle needs the same variable in its environment. Launching threadle from a GUI rather than a shell is the usual reason it doesn’t see it.

The Cursor agent binary is resolved specially, because GUI-launched processes often lack ~/.local/bin on PATH: CURSOR_AGENT_PATH, then ~/.local/bin/agent, ~/.cursor/bin/agent, /usr/local/bin/agent, /opt/homebrew/bin/agent, then PATH.


The preflight runs before any agent work, and reports every blocker at once. Muting a node exempts it from all checks — it is the fastest way past a blocker you don’t need for this run.

BlockerWhy it can’t be deferred
Agent with no modelThere is no inspector to ask in
Unwired required inputThe node would starve
Context node with no payloadDetached cannot distill mid-run
Skill or rules node with no fileNothing to inject
Illegal cross-frame wireThe expansion is ambiguous

Gates are the one non-structural case: approval nodes, live handoffs, and tripwires set to park all need --approve-all, which auto-forwards the text without an edit step. If you want to splice, run on the canvas.

A context node whose payloadHash points at a blob you have since deleted fails differently:

detached run blocked — context "<label>" payload is missing (re-materialize or mute)

Running a context extraction node detached is refused outright — context extraction nodes are not yet supported in detached runs — materialize the payload first, mute the node, or run interactively.


Custom nodes are child processes with strict contracts. Each failure mode has its own message:

FailureMessage
Non-zero exitThe process error, with stderr on the ! lane of the run log
TimeoutThe process is killed at the manifest’s timeoutMs — default 60 000 ms, hard-capped at 300 000 ms. Testing a single node reports timed out after <timeoutMs>ms
Output over 4 MBThe run fails — that is the hard output cap
Input doesn’t match the declared type<node> expects <type> input but not an int: "…"
Named-output node printed non-JSON<node> declares named outputs but did not print a JSON object
Named-output node printed an array or scalar<node> declares named outputs but printed an array
Declared output port absent<node>: output is missing port "<port>"
Required input port unfed<node>: required input "<port>" has no value
Too many at oncetoo many custom nodes running (max 8) — try again in a moment

stderr is captured whether or not the node failed — the first 4 000 characters are written to the run log, and the first 2 000 to the per-job log under runs/logs/. A node that “does nothing” usually printed its real output to stderr.

Value types are text, int, float, bool, json. text accepts anything; int widens to float; everything else must parse exactly.


threadle detects the classic split — fresh UI running against an old API process, or the reverse — by comparing its own bundle’s mtime against boot time and reporting both through /api/health. A banner appears at the top of the page:

  • The running server is an older build than the code on disk — restart it.
  • A newer UI build is available — hard-refresh this page (⇧⌘R).

The web check allows 30 seconds of slack, so a build finishing mid-session won’t nag you spuriously.


opencode’s SQLite schema moves. At open time threadle checks session for id, title, directory, parent_id, agent, time_updated. If any is missing — or the file cannot be opened — it logs once and falls back to opencode db --format json for every query:

threadle: opencode.db session table schema unexpected; using `opencode db` CLI fallback
threadle: cannot open opencode.db directly (<err>); using CLI fallback

The direct connection is also retried up to three times on BUSY/LOCKED first. In fallback mode sessions still list and read; it is slower, and it needs the opencode binary on PATH.


threadle prefers any local copy of the price table over the network, regardless of age: opencode’s ~/.cache/opencode/models.json first, then its own ~/.config/threadle/models-pricing.json, freshest wins. The network is contacted automatically only when neither exists.

With no local copy and no connectivity, the price table comes back empty and Statistics shows source: unavailable (offline and no local cache); tracked cost figures are absent — token counts and actual spend still work. Press ⟳ fetch prices in Statistics once you are online; a failed fetch returns the HTTP status or the network error, and the automatic attempt backs off rather than retrying in a loop.


  • CLI — jobs, inventory (services, agents, …), exit codes, log lanes
  • Files & config — where run logs are kept
  • Providers — how each adapter discovers and runs
  • Settings — pricing source and billing mode