Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneeszack

Relationships

#3244 swamp model cancel <model> cancels a run of a different model of the same type and reports it under the named model

Opened by zack · 10/11/2026· Shipped 10/11/2026

Severity: high — the command silently kills a method run belonging to a model the user did not name, exits 0, and prints the named model's name against the other model's run id, so neither the user nor a script can tell the wrong run was stopped.

Affects: swamp 20261009.215038.0-sha.986f9022 (source HEAD dbd7ba9a still has it; src/cli/commands/model_cancel.ts and src/domain/models/active_run.ts are identical between 986f9022 and dbd7ba9a)

Summary

swamp model cancel <model> resolves the named model, but then selects the run to cancel by model type only. With two models of the same type (for example two command/shell models), naming the idle one cancels the other one's running method run. The command exits 0 and its output says modelName: <the idle model>, while the run id it prints, the process it killed, and the history record marked cancelled all belong to the other model.

Steps to reproduce

mkdir /tmp/cancel-repro && cd /tmp/cancel-repro
swamp init
swamp model create command/shell idle    --global-arg run='echo hi'
swamp model create command/shell sleeper --global-arg run='exec sleep 40'

# start a run of "sleeper" only; "idle" is never run
(swamp model method run sleeper execute; echo "sleeper exit=$?") &
sleep 5

swamp model cancel idle --json; echo "cancel exit=$?"
wait
swamp model method history get sleeper --json

Expected behaviour

swamp model cancel idle fails with exit 1 and No running method runs found for model 'idle', and the sleeper run keeps running.

What establishes this:

  • The command takes a model, not a type: design/primitives/workflows.md:2941 documents swamp model cancel <model> [--all] [--reason <reason>]; the help shows swamp model cancel [model_id_or_name] with the example "Cancel a model run: swamp model cancel my-server".
  • The source comment reads // Cancel a specific model's running output (src/cli/commands/model_cancel.ts:428), and the error it raises is worded per model: No running method runs found for model '<name>' (line 446).
  • .claude/skills/swamp/references/model/reference.md:510-511 separates "Cancel a specific model's running method: swamp model cancel <name>" from "Cancel all running model methods: swamp model cancel --all".
  • The command's own output attributes the cancelled run to the named model (modelName), which is false in this case.
  • The original implementation was per model (see Root cause).

Actual behaviour

$ swamp model cancel idle --json; echo "cancel exit=$?"
{"id":"fd70bf09-25a9-4b05-932b-50c7d876b96a","modelName":"idle","type":"command/shell","method":"execute","status":"cancelled","reason":null,"skipped":[]}
cancel exit=0
sleeper exit=1

The id reported under modelName: idle is the sleeper run, which is now recorded as cancelled:

$ swamp model method history get sleeper --json
{
  "id": "fd70bf09-25a9-4b05-932b-50c7d876b96a",
  "modelName": "sleeper",
  "type": "command/shell",
  "methodName": "execute",
  "status": "cancelled",
  ...
  "error": { "message": "aborted" }
}

Running swamp model cancel idle --json again, now that nothing of that type is running, gives the expected error and exit 1:

{
  "error": "No running method runs found for model 'idle'"
}

Root cause

src/cli/commands/model_cancel.ts:439-442 (at 986f9022):

const runningRows = runTracker.findAllRunning();
const trackerRuns = runningRows.filter(
  (r) => r.modelType === type.normalized,
);

The resolved definition is used only for the name in messages and JSON output (lines 446, 479-482, 489). The tracker row cannot be filtered more narrowly as it stands: ActiveRunData (src/domain/models/active_run.ts:36-58) carries modelType and methodName but no definition id or model name.

Introduced by fe08c4dc, "feat(run-tracker): SQLite run tracker for in-flight run lifecycle (swamp-club#655) (#1725)". The original command (89973acb, #808 / #1683) selected runs with o.definitionId === definition.id && o.status === "running"; fe08c4dc replaced that line with the type filter. Its commit message says only that model cancel "now queries the tracker instead of scanning output YAML" and does not mention widening the scope, and design/enablers/run-tracker.md does not describe type-wide selection either.

A fix likely needs the tracker row to record the definition id (or the cancel path to map tracker run ids back to their output records' definitionId).

How the UAT found it

Test file tests/cli/model/cancel_test.ts in swamp-uat (on the test-investigation branch, not yet merged), test "swamp model cancel on an idle model leaves another model's run of the same type running".

Before the audit this test did not exist. The existing cancel tests each installed a single model: the "no running runs" test cancelled shell-echo in a repo where nothing at all was running, and the successful-cancel tests ran and cancelled only shell-cancellable. With one model per type in every repo, a type filter and a definition filter select the same rows, so the tests could not distinguish them.

The new test installs two command/shell models (shell-echo, idle, and shell-cancellable, running), runs swamp model cancel shell-echo --json, and asserts exit 1 with No running method runs found for model 'shell-echo'. It then cancels shell-cancellable by name and asserts that the output names shell-cancellable, that the run process exits 1, and that the model's history holds exactly that run id as cancelled. It fails at the first assertion:

AssertionError: Command "swamp model cancel shell-echo --json" was expected to fail but exited 0:
{"id":"...","modelName":"shell-echo","type":"command/shell","method":"execute","status":"cancelled","reason":null,"skipped":[]}

Run it with:

deno test --allow-read --allow-write --allow-env --allow-run --allow-net \
  --filter "leaves another model's run" tests/cli/model/cancel_test.ts
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 3 MOREPLAN REVISION+ 15 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/11/2026, 3:53:00 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/11/2026, 2:31:15 PM

Sign in to post a ripple.