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· Shipped 8/27/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 list → X 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, worksSame 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=2A --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)
Shipped
Click a lifecycle step above to view its details.