Relationships
#2514 A suspended run started by swamp serve cannot be cancelled
Opened by hammz · 9/24/2026· Shipped 9/28/2026
Problem
A run started through swamp serve records its serve instance (instanceId). From the dashboard, workflow run --server, or a webhook, it is set by run.start(Deno.pid, ctx.instanceId) (src/serve/handlers/workflow_handlers.ts, src/serve/webhook.ts). Once such a run is suspended, nothing can cancel it:
- Local
swamp workflow cancel <wf> --run <id>refuses any serve-owned run, whatever its status: "belongs to a serve instance and cannot be cancelled locally. Use --server" (src/cli/commands/workflow_cancel.ts,isServeOwnedRun). swamp workflow cancel --run <id> --server <url>and the WebSocketcancellook only at in-memory registries:cancelRegistry,activeRunRegistry, and scheduled runs (cancelExecutioninsrc/cli/commands/serve.ts;src/serve/connection.ts). A suspended run leaves those when it suspends, so the request returns 404 "No active workflow-run with id … in this serve instance".- Supersede skips serve-owned runs (
src/libswamp/workflows/supersede.ts). rejectneeds a gate inwaiting_approval, so it cannot clear a run whose gates are already approved.
The design doc says to "cancel those through the serve API" (supersede) and, for an expired gate, "The run stays suspended; cancel it to clear it." Neither works for a serve-owned run.
Where it shows up
- An expired approval gate on a serve-started run: approve and reject are both refused, and the run stays suspended for good.
- swamp-club#2498: resume refuses a suspended run whose workflow changed shape. For a serve-owned run, the refusal can only say to revert the workflow change and resume, because no cancel path works.
- Clutter: a stuck run keeps appearing in
swamp workflow approvalsand on the dashboard with a Resume action. A later suspended run of the same workflow then makes a bareapprove/resumefail with "Multiple suspended runs".
Suggested fix
Have serve's cancel (HTTP and WebSocket) fall back to a persisted suspended run it owns. Load it from the run repository, then run.cancel(reason) and save. That needs a way to find a run by id alone, since the cancel request carries no workflow name. It also needs the same authorization and audit as cancelling an active run.
If local cancel is instead allowed for a serve-owned run that is suspended, it must not call killProcessTree(run.pid). That pid is the serve process, which passes the isSwampProcess check. Local cancel also races an auto-resume that serve may be launching at that moment.
Once a fix lands, update the #2498 refusal for serve-owned runs (suspendedWayOut in src/domain/workflows/resume_reset.ts) to name the working cancel command.
Acceptance
- A serve-owned suspended run can be cancelled through serve, over both HTTP and WebSocket, and the cancellation is audited.
- An expired-gate run started from the dashboard can be cleared.
- The #2498 refusal for serve-owned runs names that command.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.