Severity: high — a safeguard is discarded and success-logged in the same breath
On the parameter-sweep path, --cost-limit and --ttl given on the command line are
silently discarded. Each per-row config is built from scratch and only three fields are
copied off the CLI base config, so nothing else survives.
The --no-detach branch then prints:
Using safeguards: ttl=4h, idle-timeout=
This log line is the most dangerous part of the bug. It affirmatively tells the user a
safeguard is active at the instant that value is being dropped, using the very variable that
is about to go unused. A user reading that line has every reason to believe the sweep is
bounded.
--cost-limit is worse than --ttl, because --ttl has a working alternative
(ttl: under defaults:, a recognised parser key) while --cost-limit has none: there
is no cost_limit: case in the parser, so writing it in the param file produces a
PARAM_cost_limit env var that caps nothing (see #526). The net effect is that a
param-file sweep has no per-instance dollar cap available at all, and --budget is
warning-only, so the only real bound on a sweep is TTL × rate × row count.
Reproduction (spawn v0.100.2, cmd/ at ad8a4e1)
spawn launch sweep-check --param-file two-rows.yaml \
--no-detach --ttl 4h --cost-limit 5
# stderr says: Using safeguards: ttl=4h, idle-timeout=
# then, on either launched instance:
aws ec2 describe-tags --filters Name=resource-id,Values=<id> \
--query 'Tags[?starts_with(Key,`spawn:`)]'
# -> no spawn:cost-limit tag; spawn:ttl reflects the param-file `ttl:`, not the CLI --ttl
A param file with no ttl: under defaults: and only a CLI --ttl 4h is the sharp
case: the CLI check at launch_sweep.go:79 is satisfied, the safeguards line prints, and the
instances get no TTL from that flag.
As with #524, the trace below is source-level rather than a live run — reproducing it costs
money by construction. It is confirmable under the Substrate emulator at no cost.
Evidence
| line |
what happens |
launch_flags.go:223 |
--ttl binds ttl |
launch_flags.go:305 |
--cost-limit binds costLimit |
launch_config.go:292-293 |
config.CostLimit = costLimit — set on the base config only |
launch_single.go:70-77 |
the sweep path never calls that builder; it hands launchParameterSweep a config with exactly Region and InstanceType |
launch_sweep.go:79 |
--no-detach requires a non-empty CLI ttl or idleTimeout — so the flag is load-bearing for an argument check … |
launch_sweep.go:83 |
… and then Using safeguards: ttl=%s, idle-timeout=%s prints it. This is the misleading affirmation. |
sweep.go:74-82 |
buildLaunchConfigFromParams: "Start with an empty config" — only sweep identity fields are seeded |
launch_sweep.go:155-176 |
the only fields copied off the base config: Region, InstanceType, Name. CostLimit, TTL, and every other launch flag are dropped |
Expected behaviour — either of these, not the current third thing
(a) Honour them as per-instance controls. Copy CostLimit/TTL (and any other
per-instance safeguard) from the base config onto each per-row config where the row does not
override it, so --cost-limit 5 means "5 dollars per row". Document which reading applies —
per row or per sweep — because the two differ by a factor of len(params).
(b) Reject them for sweeps. Error out with something actionable:
--cost-limit is not supported with --param-file; set cost_limit: per row (once that key
exists), or simply --cost-limit does not apply to parameter sweeps.
Either is fine. Silent discard with a success log is not. If (b), the safeguards line at
:83 must stop claiming a ttl the instances will not receive — print the value that
actually reaches them, or say nothing.
If param-file fields are the intended interface, then the corollary in #526 is part of
this fix: an unknown key like cost_limit: must error, not become PARAM_cost_limit.
Otherwise the documented workaround for this issue fails as quietly as the bug it replaces.
Regression test to add
given a 2-row param file with NO ttl: in defaults, launched with --no-detach --ttl 4h --cost-limit 5
when launch runs
then EITHER each launched config carries TTL=4h and CostLimit=5
OR the command fails before launching, naming the unsupported flag
and in both cases: no stderr line claims a safeguard that is not on the instances
Assert on the per-row LaunchConfig values that reach provisioning (or the resulting
spawn:ttl / spawn:cost-limit tags), not on the CLI variables — the CLI variables are
correct today; the bug is that they never travel.
Cross-references
Found while building a heterogeneous GROMACS benchmark sweep for the cost-to-result
project; recorded there as finding 15 in docs/platform-findings.md. A draft matrix in that
project carried cost_limit: 8 under defaults: labelled "the enforced cap" — it was
enforcing nothing, and only grepping cmd/sweep.go revealed it. That is the exact
misconfiguration this issue predicts a user will write.
Severity: high — a safeguard is discarded and success-logged in the same breath
On the parameter-sweep path,
--cost-limitand--ttlgiven on the command line aresilently discarded. Each per-row config is built from scratch and only three fields are
copied off the CLI base config, so nothing else survives.
The
--no-detachbranch then prints:This log line is the most dangerous part of the bug. It affirmatively tells the user a
safeguard is active at the instant that value is being dropped, using the very variable that
is about to go unused. A user reading that line has every reason to believe the sweep is
bounded.
--cost-limitis worse than--ttl, because--ttlhas a working alternative(
ttl:underdefaults:, a recognised parser key) while--cost-limithas none: thereis no
cost_limit:case in the parser, so writing it in the param file produces aPARAM_cost_limitenv var that caps nothing (see #526). The net effect is that aparam-file sweep has no per-instance dollar cap available at all, and
--budgetiswarning-only, so the only real bound on a sweep is TTL × rate × row count.
Reproduction (spawn v0.100.2,
cmd/atad8a4e1)A param file with no
ttl:underdefaults:and only a CLI--ttl 4his the sharpcase: the CLI check at
launch_sweep.go:79is satisfied, the safeguards line prints, and theinstances get no TTL from that flag.
As with #524, the trace below is source-level rather than a live run — reproducing it costs
money by construction. It is confirmable under the Substrate emulator at no cost.
Evidence
launch_flags.go:223--ttlbindsttllaunch_flags.go:305--cost-limitbindscostLimitlaunch_config.go:292-293config.CostLimit = costLimit— set on the base config onlylaunch_single.go:70-77launchParameterSweepa config with exactlyRegionandInstanceTypelaunch_sweep.go:79--no-detachrequires a non-empty CLIttloridleTimeout— so the flag is load-bearing for an argument check …launch_sweep.go:83Using safeguards: ttl=%s, idle-timeout=%sprints it. This is the misleading affirmation.sweep.go:74-82buildLaunchConfigFromParams: "Start with an empty config" — only sweep identity fields are seededlaunch_sweep.go:155-176Region,InstanceType,Name.CostLimit,TTL, and every other launch flag are droppedExpected behaviour — either of these, not the current third thing
(a) Honour them as per-instance controls. Copy
CostLimit/TTL(and any otherper-instance safeguard) from the base config onto each per-row config where the row does not
override it, so
--cost-limit 5means "5 dollars per row". Document which reading applies —per row or per sweep — because the two differ by a factor of
len(params).(b) Reject them for sweeps. Error out with something actionable:
--cost-limit is not supported with --param-file; set cost_limit: per row(once that keyexists), or simply
--cost-limit does not apply to parameter sweeps.Either is fine. Silent discard with a success log is not. If (b), the safeguards line at
:83must stop claiming attlthe instances will not receive — print the value thatactually reaches them, or say nothing.
If param-file fields are the intended interface, then the corollary in #526 is part of
this fix: an unknown key like
cost_limit:must error, not becomePARAM_cost_limit.Otherwise the documented workaround for this issue fails as quietly as the bug it replaces.
Regression test to add
Assert on the per-row
LaunchConfigvalues that reach provisioning (or the resultingspawn:ttl/spawn:cost-limittags), not on the CLI variables — the CLI variables arecorrect today; the bug is that they never travel.
Cross-references
--estimate-onlyis dropped on the same path, with the same root cause (per-rowconfigs built from scratch, dispatch happening before the check). --estimate-only is ignored on the foreground sweep path — --no-detach launches every row instead of estimating #524 is the P0 of the
pair because it fires on the command a user runs specifically to spend nothing.
PARAM_*, which closes off theworkaround.
isn't applied, that one about a figure that may be incomplete.
Found while building a heterogeneous GROMACS benchmark sweep for the
cost-to-resultproject; recorded there as finding 15 in
docs/platform-findings.md. A draft matrix in thatproject carried
cost_limit: 8underdefaults:labelled "the enforced cap" — it wasenforcing nothing, and only grepping
cmd/sweep.gorevealed it. That is the exactmisconfiguration this issue predicts a user will write.