Relationships
#3740 serve: webhook-triggered workflow runs can't be cancelled — cancel can't find them, so it reports serve's own pid as "the process running it is still alive"
Opened by zack · 10/21/2026· Shipped 10/21/2026
Summary
A webhook-triggered workflow run can't be cancelled through serve. swamp workflow cancel --server … --run <id> refuses it with:
{
"error": "Run <id> is recorded as running and was not cancelled: the process running it on the serve host is still alive. Stop that process, then cancel the run again"
}The "process still alive" is the serve instance handling the cancel: serve drives the run, and its tracker row holds serve's own pid. No restart, crash or pid reuse is involved. The only way out is to stop serve, which aborts the run during the shutdown drain. Seen in the field with run 65eb7534-c40b-47e9-a287-41bc3edbbbda, which had steps dispatched to a worker. A worker doesn't change who owns the run: workers never write the run tracker.
Reproduction (verified, swamp 20261021.155838.0-sha.f979f7c1, filesystem datastore, auth-mode none)
- Repo with a
command/shellbehaviorsleeperand a workflowsleepwfwhose single step runsexecutewithrun: sleep 600. swamp serve --port 9125 --webhook '/hooks/test:sleepwf:s3cret'- Send a signed GitHub-style POST:
BODY='{"action":"test"}' SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac s3cret | awk '{print $NF}') curl -X POST http://127.0.0.1:9125/hooks/test -H 'Content-Type: application/json' \ -H 'X-GitHub-Event: push' -H "X-Hub-Signature-256: sha256=$SIG" -d "$BODY" # {"status":"queued","workflow":"sleepwf"}
- Run
bf9e5feb…starts. Its tracker row ispid=3465527, status=running, and 3465527 is the serve process. swamp workflow cancel sleepwf --run bf9e5feb… --server ws://127.0.0.1:9125→ the error above.- Control case: the same workflow triggered by cron (
trigger.schedule: "* * * * *") on the same serve cancels fine (cancellation_requested).
Cause
cancelExecution (src/cli/commands/serve.ts:741) looks for a run in:
cancelRegistry: non-detached WebSocket runsactiveRunRegistry: detached API runs and resumesscheduledCancelByRunId(ScheduledExecutionService.cancelByRunId): cron runs
Webhook runs are in none of these. WebhookService keeps its own running map keyed by an internal exec id (src/serve/webhook.ts:576, :1051), with no lookup by run id and no hook into cancelExecution. The cancel then falls through to cancelSuspendedRunAndPush → ownerGoneDecider (src/serve/suspended_run_cancel.ts). That finds a tracker row on this host whose pid is alive (serve's own), and it returns OWNER_PROCESS_ALIVE as a 409 conflict.
/api/v1/cancel (cancel all) skips webhook runs for the same reason. It only counts cancelRegistry, activeRunRegistry and scheduledExecution.cancelAllRuns.
Probably also affected (not verified)
- Nested child runs of cron and webhook runs:
cancelByRunIdmatches only the top-level run id, and the detached-run path is the only one that records nested run ids (registry.addNestedRun). ownerGoneDeciderreports "process still alive" even when that pid isDeno.pid(serve itself). Whatever the lookup gap, a row carrying serve's own pid for a run serve can't find shouldn't produce advice to "stop that process".
Expected
workflow cancel --server (and /api/v1/cancel) cancels a webhook-triggered run the way it cancels cron and API runs: by aborting the run's controller.
Related
- swamp-club#3739: a different way to reach the same error (pid reuse after a container restart).
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.