Troubleshooting
Most failures are refusals, not crashes: threadle preflights a run and stops before spending money. The message names the node.
| Problem | Cause | Fix |
|---|---|---|
| No sessions listed for a provider | Its storage directory does not exist where threadle looks | Install 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 blockers | Fix the named nodes, or mute them |
model not set — pick one in the inspector | An agent node has no model | Pick a model in the inspector — detached runs cannot ask |
required input not wired | A node that needs an input has none | Wire it, or mute it. ⌀ Loose ends lists them all |
context not materialized | A context node has no payload hash | Materialize it in the Library first, or mute the node |
this workflow has N interactive gate(s) … — pass approveAll | Approval or live-handoff nodes in a server / detached run | Add --approve-all, use ≫ and confirm, or run in-tab with ▶ for splice |
threadle server not reachable | status / logs / run --detach with no server | Start threadle --no-open first |
custom node "<name>" is not installed | The graph references a node folder that isn’t on this machine | Import or create the node under ~/.config/threadle/nodes/ |
nothing to run for "<target>" | Not a template id, file path, or graph id | threadle --list-templates, or check the path |
cycle detected — workflow can never finish | The graph has a loop | Break the cycle — Loose ends flags unreachable nodes |
EADDRINUSE at startup | Port 4570 already taken | threadle --port 4600 |
403 forbidden host / cross-origin write rejected | Reaching the API from a non-loopback hostname | Use 127.0.0.1, localhost, or [::1] |
| Features silently not working | Browser tab and server are different builds | Follow the banner — see below |
source: unavailable (offline and no local cache) | No local price table and no network | See below |
No sessions found
Section titled “No sessions found”Availability is checked per provider and nothing cascades — a missing provider contributes no sessions and breaks nothing else.
| Provider | Reported 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.
A detached run refuses to start
Section titled “A detached run refuses to start”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.
| Blocker | Why it can’t be deferred |
|---|---|
| Agent with no model | There is no inspector to ask in |
| Unwired required input | The node would starve |
| Context node with no payload | Detached cannot distill mid-run |
| Skill or rules node with no file | Nothing to inject |
| Illegal cross-frame wire | The 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.
A custom node fails
Section titled “A custom node fails”Custom nodes are child processes with strict contracts. Each failure mode has its own message:
| Failure | Message |
|---|---|
| Non-zero exit | The process error, with stderr on the ! lane of the run log |
| Timeout | The 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 MB | The 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 once | too 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.
Stale build
Section titled “Stale build”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 schema churn
Section titled “opencode schema churn”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 fallbackthreadle: cannot open opencode.db directly (<err>); using CLI fallbackThe 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.
Prices unavailable offline
Section titled “Prices unavailable offline”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.
Related
Section titled “Related”- 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