Repository navigation
Description
Written-by: FlyBot Product
plugins/workflow/** belongs to the workflow module, not to us: this is a defect report. We do not implement it and will not open a PR for it.
What happens
Deleting a user-owned workflow while it has a live run releases no run Bot — fb.bot.remove is never called — and leaves the run behind as running. Nothing is repaired until the next plugin load, when reap() releases it.
Reproduction (live main, 09151516, in-process service, no host)
const released = []
const svc = createWorkflowService({ persist: false, now: () => 1_700_000_000_000, bots: () => [],
spawn: async (spec) => `runbot-${spec.nodeId}`,
release: async (id) => { released.push(id) },
dispatch: async () => ({ ok: true }), warn: () => {} })
const made = svc.create({ name: 'Probe lane', nodes: [{ id: 'plan', name: 'Planner', prompt: 'x' }], edges: [] })
const id = made.workflows.find(w => w.name === 'Probe lane').id
await svc.runNamed({ workflow: 'Probe lane', brief: 'probe', task: 't1' })
const del = await svc.delete({ workflowId: id })Output:
started.ok = true spawned = [ 'runbot-plan' ] released = []
run status = running ephemeral = {"plan":"runbot-plan"}
delete.ok = true released after delete = []
workflow still listed = false
With persist: true in a temp dir, workflows.json after the same delete still carries the lane:
workflow listed = false
lane kept = [{"id":"run-c9337392","status":"running","ephemeral":{"plan":"runbot-plan"}}]
state.runs keys = 0
released = []
Root cause
plugins/workflow/src/service.js:647-648—const run = state.runs[workflowId]; const releasing = run ? Object.values(run.ephemeral ?? {}) : []reads the legacystate.runscontainer.migrateRuns(:1103) moves every old run intostate.lanesand then empties that container (:1124state.runs = {}). A new run only ever goes tolaneOf(id).active(:190,:492), with itsephemeralmap on the run object (:484).delete()(:640-664) never mentionsstate.lanes, soreleasingis always empty,finish()runs, filters the workflow out, and the lane stays untouched.#1437(b20524fe) reworkeddispose()(:999-1015) so it walks every lane, releases those Bots first and only then drops their ids — but it did not touchdelete(), which (:640-664) still reads the emptiedstate.runsat:647-648. So this defect stays, and the stranded ids are still only released when the plugin is disposed or reloaded (plugin load runsreap()).
Nothing else repairs it: once the workflow is gone advance answers No such workflow (:576-577), so the run never reaches done/failed and releaseRun (:387-399) is never called. reap() does walk every lane (:968) and releases a run still marked running, but it only runs at plugin load — until then the run Bots stay in the host (release is fb.bot.remove, plugins/workflow/index.js:65-70), and the lane, queue included, stays in workflows.json. That queue can never drain either: takeNext iterates state.workflows (:402), where the workflow no longer exists.
Docs this falsifies
docs/subsystems/workflow.md:31 — "delete removes the workflow, releases ephemeral Bots, and cleans logs/runs/routes" — and :41 — "workflow/delete … releases its ephemeral Bots if a run exists".
The existing test cannot catch it
plugins/workflow/test/assign.test.js:324 is named "delete removes workflow and releases its ephemeral Bots", but it never spawns and never starts a run: released stays [], and no assertion reads it.
Reachable today
#1435 gives every user-owned workflow row a × (core/src/client/workflows.js, Row); the confirm dialog only says the delete cannot be undone and never looks for a live run. One click during a run hits this.
Expected
delete should collect the ids from laneOf(id).active[].ephemeral, releaseAll them, drop the lane (delete state.lanes[id], queue and history included), and only then remove the workflow. The confirm dialog should say the live run will be stopped.
Acceptance
- Rewrite
plugins/workflow/test/assign.test.js:324to start a real run (spawnreturns an id,releaserecords it) and then delete:releasedcontains that id, andstate().workflowsno longer lists the workflow. - With
persist: true, the file after the delete has nolanes[workflowId]— noactive, noqueue, nohistory. docs/subsystems/workflow.md:31and:41either match the new behaviour or say that the run is stopped and its Bots released.
Activity
HugoXK commentedon Oct 27, 2026
Closing: this was filed under the superseded "report only, we do not implement
plugins/workflow/**" policy (that line is still in the body). No implementation was started and there is no PR, so under the new rule the issue comes down rather than sitting as a claim without a PR. Will re-file with the reproduction and the PR together when a Dev picks it up; the leak is still real and is not fixed by#1476or#1474.