Severity: high — the documented way to check for leaked instances cannot fail
spawn list --state all returns an empty list always, regardless of how many instances
exist. The string all is passed through as a literal EC2 instance-state-name filter value,
and no instance is ever in state "all", so the filter matches nothing. Exit code is 0 and the
output is a well-formed empty array, so it reads exactly like a clean account.
pkg/aws/client.go:996-1008:
// Add state filter if specified
if stateFilter != "" {
filters = append(filters, types.Filter{
Name: aws.String("instance-state-name"),
Values: []string{stateFilter}, // <-- "all" goes straight through
})
} else {
// Default: show running and stopped instances (not terminated)
filters = append(filters, types.Filter{
Name: aws.String("instance-state-name"),
Values: []string{"pending", "running", "stopping", "stopped"},
})
}
There is no "all" case anywhere on the path — grep -n '"all"' cmd/list.go pkg/aws/client.go
returns nothing. cmd/list.go:45 binds --state as a free-form string
("Filter by instance state (running, stopped, etc.)") and cmd/list.go:81 hands it to
ListInstances unmodified.
So the flag whose name promises a superset of the default actually produces the empty set —
strictly less than passing no flag at all.
This is documented, which is what makes it costly
docs/tools/spawn.md:95 in the spore-host docs repo:
### `spawn list`
List your running (or all) instances:
spawn list
spawn list --state all # <-- documented; returns nothing
spawn list --region us-east-1
A user following the docs to answer "did anything leak?" gets [] and stops looking. That is
the same failure shape as #47 (e2e tests leaking 67 instances, including one running 12 days):
the leak was invisible until someone checked a different way.
Reproduction
spawn launch listcheck --region us-west-2 --ttl 1h -y
spawn list -o json # -> the instance
spawn list --state all -o json # -> [] ← wrong
spawn list --state running -o json # -> the instance
Expected behaviour
--state all should mean no state filter at all — every state including terminated and
shutting-down, i.e. a genuine superset of the default. Concretely: special-case
all (and probably any) in listInstancesInRegion to append no
instance-state-name filter.
The second half matters as much: an unrecognised --state value must be an error, not an
empty result. --state runing, --state ALL, --state active all currently return []
with exit 0. Validate against the EC2 enum (pending, running, shutting-down,
terminated, stopping, stopped) plus the all alias, and reject anything else naming the
valid values. Without that, this bug just moves to the next plausible-sounding word.
Note that fixing all to include terminated will change what the command prints in normal
use — a long-lived account accumulates terminated instances that AWS retains for about an
hour. That is the correct behaviour for a flag called all, and it is why the default (which
deliberately excludes terminated) should stay exactly as it is.
Regression test to add
Tier 0, against Substrate:
given one running instance and one terminated instance
when spawn list --state all -o json
then BOTH are returned
when spawn list -o json (no flag)
then only the running one is returned
when spawn list --state bogus -o json
then exit != 0 with an error naming the valid states (NOT exit 0 and [])
The third case is the important one. This bug is a check that cannot fail, so a test that only
asserts --state all returns something would still pass if the flag silently degraded to
some other wrong-but-non-empty filter. Assert the exact membership, and assert that garbage is
rejected.
Cross-references
Found while writing the #524 regression test for the cost-to-result project. That project had
also recorded spawn list --state all -o json --region us-west-2 → [] as evidence that its
AWS account was clean before any spend; that evidence was worthless and has been retracted and
re-taken with bare spawn list.
Severity: high — the documented way to check for leaked instances cannot fail
spawn list --state allreturns an empty list always, regardless of how many instancesexist. The string
allis passed through as a literal EC2instance-state-namefilter value,and no instance is ever in state
"all", so the filter matches nothing. Exit code is 0 and theoutput is a well-formed empty array, so it reads exactly like a clean account.
pkg/aws/client.go:996-1008:There is no
"all"case anywhere on the path —grep -n '"all"' cmd/list.go pkg/aws/client.goreturns nothing.
cmd/list.go:45binds--stateas a free-form string(
"Filter by instance state (running, stopped, etc.)") andcmd/list.go:81hands it toListInstancesunmodified.So the flag whose name promises a superset of the default actually produces the empty set —
strictly less than passing no flag at all.
This is documented, which is what makes it costly
docs/tools/spawn.md:95in thespore-hostdocs repo:A user following the docs to answer "did anything leak?" gets
[]and stops looking. That isthe same failure shape as #47 (e2e tests leaking 67 instances, including one running 12 days):
the leak was invisible until someone checked a different way.
Reproduction
Expected behaviour
--state allshould mean no state filter at all — every state includingterminatedandshutting-down, i.e. a genuine superset of the default. Concretely: special-caseall(and probablyany) inlistInstancesInRegionto append noinstance-state-namefilter.The second half matters as much: an unrecognised
--statevalue must be an error, not anempty result.
--state runing,--state ALL,--state activeall currently return[]with exit 0. Validate against the EC2 enum (
pending,running,shutting-down,terminated,stopping,stopped) plus theallalias, and reject anything else naming thevalid values. Without that, this bug just moves to the next plausible-sounding word.
Note that fixing
allto includeterminatedwill change what the command prints in normaluse — a long-lived account accumulates terminated instances that AWS retains for about an
hour. That is the correct behaviour for a flag called
all, and it is why the default (whichdeliberately excludes terminated) should stay exactly as it is.
Regression test to add
Tier 0, against Substrate:
The third case is the important one. This bug is a check that cannot fail, so a test that only
asserts
--state allreturns something would still pass if the flag silently degraded tosome other wrong-but-non-empty filter. Assert the exact membership, and assert that garbage is
rejected.
Cross-references
a user would reach for to do that check from inside the tool.
removablenever consults State #516, orphans: unassociated EIPs are reported unconditionally (no spawn/principal attribution), and both remediation hints are wrong — one is destructive #500 — same family: lifecycle/inventory commands reporting a state that isn't theactual state.
originally specified to verify "zero instances launched" via
spawn list --state all;that assertion would have passed unconditionally, which is how this bug was found. That test
now uses bare
spawn listplus a direct EC2 describe instead.Found while writing the #524 regression test for the
cost-to-resultproject. That project hadalso recorded
spawn list --state all -o json --region us-west-2→[]as evidence that itsAWS account was clean before any spend; that evidence was worthless and has been retracted and
re-taken with bare
spawn list.