Skip to main content
← Back to list
01Issue
BugOpenSwamp CLIPublicTeam
AssigneesNone

Relationships

#1759 `search` → `list` aliases are hand-rolled Command clones, not aliases: 10 of 11 have drifted from their parent's option set

Opened by magistr · 8/21/2026

Problem

Every <family> list alias is built as a new Command that shares only the action handler and hand-copies a subset of the parent's options. Example from src/cli/commands/workflow.ts:

.command("search", workflowSearchCommand)
.command(
  "list",
  new Command()
    .description("Alias for workflow search")
    .hidden()
    .arguments("[query:string]")
    .option("--repo-dir <dir:string>", "Repository directory (env: SWAMP_REPO_DIR)")
    .action(workflowSearchAction),   // shares the action, not the options
);

Because the option surface is copied by hand, it drifts every time an option is added to the parent. It has drifted on 10 of the 11 self-declared aliases:

alias (X listX search) options missing vs parent
workflow run list --since --status --workflow --tag --input --limit --server --token
workflow history list --input --server --token
model list --server --token
model method history list --server --token
model output list --server --token
model type list --server --token
workflow list --server --token
vault list --server --token
vault type list --server --token
report type list --server --token
datastore type list (none — but its parent declares zero options)

datastore type list is the only one at parity, and only because datastore type search has no options at all. No alias with a non-empty option set is correct.

Worst case is workflow run list, which exposes 1 of its parent's 9 options:

$ swamp help workflow run search | jq -c '[.root.options[].flags]'
["--repo-dir","--since","--status","--workflow","--tag","--input","--limit","--server","--token"]
$ swamp help workflow run list   | jq -c '[.root.options[].flags]'
["--repo-dir"]

model list shows the drift is incremental and ongoing — it tracked --all (synced at some point) but not --server (broken again by the next change).

Repro

$ swamp workflow list --server http://127.0.0.1:18899 --json
{"error": "Unknown option \"--server\". Did you mean option \"--help\"?"}

$ swamp workflow search --server http://127.0.0.1:18899 --json
{"query":"","results":[ ... ]}          # same server, works

Same for model list, vault list, model type list, report type list, vault type list, model output list, model method history list.

Secondary defect: --json can emit ANSI help text

Which failure you get is arbitrary. Some drifted aliases return a parseable error; others dump colored help to stdout on a --json invocation and exit 2, so the output cannot be piped to jq:

$ swamp workflow list --server http://x --json      # {"error": ...}          rc=1
$ swamp workflow run list --limit 2 --json          # ANSI help text          rc=2
$ swamp model type list --server http://x --json    # ANSI help text          rc=2

A --json invocation should never emit ANSI help. This is arguably its own issue; noted here because it's how the alias drift surfaces in automation.

Root cause and suggested fix

The correct idiom is already used elsewhere in this codebase — Cliffy's native .alias(), in extension list/ls, model type describe/get, auth whoami/status, issue ripple/comment, vault put/write-secret, extension rm/remove, summarise/summarize, access grant/policy.

A native alias is the same Command object, so it cannot drift, and it inherits the full option set:

$ swamp extension list --server http://127.0.0.1:1 --json
{"error":"Could not connect to ws://127.0.0.1:1/: ..."}    # parsed, reached network
$ swamp extension ls   --server http://127.0.0.1:1 --json
{"error":"Could not connect to ws://127.0.0.1:1/: ..."}    # identical

.alias() also keeps the alternate name out of the help tree already (swamp help extension lists list, not ls), so the .hidden() call the current clones rely on is not needed and there is no help-output regression.

Fix: replace each hand-rolled list clone with .alias("list") on the search command it aliases, e.g.

// src/cli/commands/workflow.ts
.command("search", workflowSearchCommand)   // where workflowSearchCommand has .alias("list")

and delete the clone blocks. This closes all 10 at once and makes the class of bug unrepresentable.

Test: #1098 (UAT Phase 1f) added alias-parity tests asserting <family> list stdout ≡ <family> search stdout. Those invoke both forms with no flags, so they pass while the option sets diverge. Add an assertion that the two commands expose identical option sets. After the structural fix this is a cheap regression guard rather than the primary defence.

Context

#1531 wired --server into 25 leaf commands via withRemoteOptions() (src/cli/remote_run.ts) but the hidden list aliases were not in scope, which is what introduced the current --server/--token gap.

Note this is separate from the list commands that are not aliases and legitimately differ from their search sibling: data list, extension list, report list.

Environment

  • client / server: 20260820.072114.0-sha.05d20b6a
  • source inspected at swamp-src @ 687b5fc4
  • platform: macOS (darwin 23.6.0)
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/21/2026, 6:45:15 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.