Skip to content

fix(workflow): deleting a user workflow while a run is live releases no run Bot #1441

Closed as not planned

Description

@HugoXK

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 legacy state.runs container.
  • migrateRuns (:1103) moves every old run into state.lanes and then empties that container (:1124 state.runs = {}). A new run only ever goes to laneOf(id).active (:190, :492), with its ephemeral map on the run object (:484).
  • delete() (:640-664) never mentions state.lanes, so releasing is always empty, finish() runs, filters the workflow out, and the lane stays untouched.
  • #1437 (b20524fe) reworked dispose() (:999-1015) so it walks every lane, releases those Bots first and only then drops their ids — but it did not touch delete(), which (:640-664) still reads the emptied state.runs at :647-648. So this defect stays, and the stranded ids are still only released when the plugin is disposed or reloaded (plugin load runs reap()).

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

  1. Rewrite plugins/workflow/test/assign.test.js:324 to start a real run (spawn returns an id, release records it) and then delete: released contains that id, and state().workflows no longer lists the workflow.
  2. With persist: true, the file after the delete has no lanes[workflowId] — no active, no queue, no history.
  3. docs/subsystems/workflow.md:31 and :41 either match the new behaviour or say that the run is stopped and its Bots released.

Activity

  1. HugoXK commented on Oct 27, 2026

    @HugoXK
    ContributorAuthor

    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 #1476 or #1474.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions