Skip to content

Sweep dispatch silently drops --cost-limit and --ttl, then logs "Using safeguards: ttl=..." #525

Description

@scttfrdmn

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:costcost estimation, budgets, accountingarea:launchlaunch path: instance-type, AMI, user-data, runbugSomething isn't workingspend-safetyBugs where a cost control does not do what it says — real-money risk

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions