Relationships
#3286 Record-typed arguments are not JSON-parsed from plain key=value, unlike array and object arguments
Opened by zack · 10/12/2026· Shipped 10/12/2026
Description
A record-typed argument (z.record(...)) given as plain key=<JSON object> on the command line is never JSON-parsed. Array-typed and object-typed arguments given the same way are parsed, so the behaviour is inconsistent, and the resulting errors are misleading.
Found while triaging swamp-club#3275, which reported the sensitive-record symptom below as a limitation of the sensitive-argument guard. The guard is behaving correctly; the value reaches it as one raw string.
Steps to reproduce
Extension model with:
globalArguments: z.object({
tokens: z.record(z.string(), z.string()).meta({ sensitive: true }).optional(),
}),swamp model create <type> demowith a--global-argwhose key istokensand whose value is a JSON object holding one plain string- Actual:
Invalid global arguments for type '...': tokens: Invalid input: expected record, received string
- Actual:
- The same command where the JSON object's only value is a vault.get expression
- Actual: refused with
Global argument 'tokens' is marked sensitive and cannot be set to a literal value ..., although every value in the map is a vault expression.
- Actual: refused with
- The same data supplied as
--global-arg 'tokens.a=<vault expression>'or--global-arg 'tokens:json=<JSON object>'is accepted and stores a real map.
For comparison, --global-arg 'users=[ ... ]' for a z.array(...) field and --global-arg 'nested={ ... }' for a z.object(...) field are both parsed from the plain form.
Cause
parseKeyValueInputs (src/cli/input_parser.ts) only JSON-parses a value when the key carries the :json suffix. coerceMethodArgs (src/domain/models/zod_type_coercion.ts) then JSON-parses string values for fields whose schema type is array or object, but has no branch for record. A record field therefore stays a string: with a literal inside it fails schema validation, and with an expression inside it is a string mixing literal text with an expression, which the sensitive guard rightly treats as a literal.
Expected
A string value for a record-typed field is JSON-parsed when it holds a JSON object, the same as an object-typed field, so the plain key=<JSON object> form works for records and the sensitive guard evaluates the map's values individually.
Environment
- swamp 20261012.132734.0-sha.39452c84
- Reproduction repo:
/tmp/swamp-repro-issue-3275(cases D and E)
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.