Severity: medium — turns a class of typos and wrong guesses into silent misconfiguration
Any param-file key the sweep parser does not recognise becomes a PARAM_<key> environment
variable, with no warning. That is the correct behaviour for intentional user parameters —
it is how a sweep passes values to a workload — but it means the parser can never say "that
isn't a thing." A typo, a renamed key, or a plausible-sounding key that simply does not exist
all produce a launch that looks completely healthy and does something other than what the
file says.
cmd/sweep.go:188-190:
default:
// All unknown fields become parameters (PARAM_* env vars)
config.Parameters[key] = fmt.Sprintf("%v", val)
Concrete failures this allows, all of which launch and bill normally:
| written in the param file |
what the user believes |
what happens |
cost_limit: 8 |
a per-instance $8 cap |
PARAM_cost_limit=8; no cap (#525) |
budget: 50 |
a sweep budget |
PARAM_budget=50; nothing |
max_concurrent: 3 |
a concurrency cap |
PARAM_max_concurrent=3; nothing (it is a CLI flag) |
instance_types: [...] |
several types |
PARAM_instance_types; every row on the CLI default type |
ttl_hours: 4 |
a TTL (misremembering ttl:) |
PARAM_ttl_hours=4; no TTL |
on-complete: terminate |
hyphen instead of underscore |
PARAM_on-complete; instance never terminates |
The last two are the ones that cost money: the user wrote a lifecycle bound, the parser
accepted the file without complaint, and the instance has no bound.
Why file this separately from #525
So it isn't lost inside it. #525 is about two specific flags; this is the mechanism that
makes every future spend-control key fail the same way. Fixing it turns a class of silent
misconfigurations into loud errors, and it is the precondition for #525 option (b)
("set cost_limit: per row") being a real fix rather than a relocation of the problem.
Expected behaviour
PARAM_* passthrough must stay — it is the feature. What is missing is a way to distinguish
"a parameter for my workload" from "a spawn setting I got wrong." Options, roughly in order
of preference:
- A reserved-name denylist that errors. Reject keys that are near-misses for real spawn
controls or CLI-only flags — cost_limit, budget, max_concurrent, instance_types,
ttl_hours, on-complete, and any recognised key written with hyphens instead of
underscores. Error message should name the right spelling or say the control is CLI-only.
Cheap, and it catches every row in the table above.
- An explicit namespace for passthrough, e.g.
params:/env: sub-map, with unknown
top-level keys erroring. Cleanest, but breaking; needs a deprecation path.
- At minimum, log every
PARAM_* derived from an unrecognised key at launch time, so
PARAM_cost_limit=8 appears in the output where a user might notice it. Weakest — it is
still a warning nobody reads — but strictly better than silence and non-breaking.
(1) plus (3) together would be a good landing spot: loud failure on the known-dangerous
names, visible listing for the rest.
Note the related trap on the same surface: unknown keys at the top level of a param file
(outside defaults:/grid:/params:) are dropped entirely — spawn's own
examples/simple-params.yaml puts region:/instance_type:/ami: there, where they are
discarded. That is arguably a separate bug, but it is the same "unrecognised input accepted
in silence" pattern and probably wants the same fix.
Regression test to add
given a param file with `cost_limit: 8` under defaults:
when the params are parsed
then parsing FAILS with an error naming cost_limit as unsupported
(and NOT a config carrying Parameters["cost_limit"])
given a param file with `my_workload_arg: 42`
when the params are parsed
then parsing succeeds and Parameters["my_workload_arg"] == "42"
The second case is as important as the first: the fix must not break intentional
passthrough, which is the whole point of the default: branch.
Cross-references
Found while building a 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 had
cost_limit: 8 under defaults: with the comment "the ENFORCED cap"; the parser accepted
it, and it capped nothing. That project's harness/check_matrix.py now carries a hardcoded
denylist of exactly these keys — a check that belongs upstream, in the parser, not in each
consumer that happens to have been burned.
Severity: medium — turns a class of typos and wrong guesses into silent misconfiguration
Any param-file key the sweep parser does not recognise becomes a
PARAM_<key>environmentvariable, with no warning. That is the correct behaviour for intentional user parameters —
it is how a sweep passes values to a workload — but it means the parser can never say "that
isn't a thing." A typo, a renamed key, or a plausible-sounding key that simply does not exist
all produce a launch that looks completely healthy and does something other than what the
file says.
cmd/sweep.go:188-190:default: // All unknown fields become parameters (PARAM_* env vars) config.Parameters[key] = fmt.Sprintf("%v", val)Concrete failures this allows, all of which launch and bill normally:
cost_limit: 8PARAM_cost_limit=8; no cap (#525)budget: 50PARAM_budget=50; nothingmax_concurrent: 3PARAM_max_concurrent=3; nothing (it is a CLI flag)instance_types: [...]PARAM_instance_types; every row on the CLI default typettl_hours: 4ttl:)PARAM_ttl_hours=4; no TTLon-complete: terminatePARAM_on-complete; instance never terminatesThe last two are the ones that cost money: the user wrote a lifecycle bound, the parser
accepted the file without complaint, and the instance has no bound.
Why file this separately from #525
So it isn't lost inside it. #525 is about two specific flags; this is the mechanism that
makes every future spend-control key fail the same way. Fixing it turns a class of silent
misconfigurations into loud errors, and it is the precondition for #525 option (b)
("set
cost_limit:per row") being a real fix rather than a relocation of the problem.Expected behaviour
PARAM_*passthrough must stay — it is the feature. What is missing is a way to distinguish"a parameter for my workload" from "a spawn setting I got wrong." Options, roughly in order
of preference:
controls or CLI-only flags —
cost_limit,budget,max_concurrent,instance_types,ttl_hours,on-complete, and any recognised key written with hyphens instead ofunderscores. Error message should name the right spelling or say the control is CLI-only.
Cheap, and it catches every row in the table above.
params:/env:sub-map, with unknowntop-level keys erroring. Cleanest, but breaking; needs a deprecation path.
PARAM_*derived from an unrecognised key at launch time, soPARAM_cost_limit=8appears in the output where a user might notice it. Weakest — it isstill a warning nobody reads — but strictly better than silence and non-breaking.
(1) plus (3) together would be a good landing spot: loud failure on the known-dangerous
names, visible listing for the rest.
Note the related trap on the same surface: unknown keys at the top level of a param file
(outside
defaults:/grid:/params:) are dropped entirely — spawn's ownexamples/simple-params.yamlputsregion:/instance_type:/ami:there, where they arediscarded. That is arguably a separate bug, but it is the same "unrecognised input accepted
in silence" pattern and probably wants the same fix.
Regression test to add
The second case is as important as the first: the fix must not break intentional
passthrough, which is the whole point of the
default:branch.Cross-references
--estimate-onlydropped on the sweep path.--cost-limit/--ttldropped on the sweep path. That issue's option (b)depends on this one.
Found while building a GROMACS benchmark sweep for the
cost-to-resultproject (recordedthere as finding 15 in
docs/platform-findings.md). A draft matrix in that project hadcost_limit: 8underdefaults:with the comment "the ENFORCED cap"; the parser acceptedit, and it capped nothing. That project's
harness/check_matrix.pynow carries a hardcodeddenylist of exactly these keys — a check that belongs upstream, in the parser, not in each
consumer that happens to have been burned.