Skip to content

--vars JSON file duplicate keys silently last-win (same class as #200, different mechanism) #326

Description

@dean0x

A --vars JSON file containing a duplicate key silently keeps the last value, with no warning:

{"x": 1, "x": 2}

compiles with x = 2. The user gets no signal that they wrote the same key twice.

This is the same class of bug as #200 (duplicate --set keys silently last-win), but a different mechanism — which is why it needs its own issue rather than riding along on the #200 fix.

Mechanism

mds build --vars f.json resolves through:

  1. crates/mds-cli/src/build.rs:515-520load_optional_vars_file delegates to mds::load_vars_file.
  2. crates/mds-core/src/lib.rs:1406load_vars_file reads the file (after the PF-004 symlink guard and the MAX_FILE_SIZE check).
  3. crates/mds-core/src/lib.rs:1429serde_json::from_str::<serde_json::Value>(&content).

Step 3 is where the duplicate disappears. serde_json's map deserializer inserts each key as it is parsed, so a repeated key overwrites the earlier entry. This is serde_json behaving as designed — JSON itself does not forbid duplicate object names — not a bug in the dependency.

Why this is not fixable the way #200 is

#200 is a CLI-side problem with a CLI-side fix: --set arrives as set_vars: Vec<(String, String)>, so crates/mds-cli/src/build.rs:549-553 can inspect the vector for repeats before inserting into the HashMap. The duplicate is still visible at that point.

Here it is not. By the time load_vars_file returns HashMap<String, Value>, the duplicate has already been collapsed inside serde_json — there is nothing left for the CLI to detect. Catching it requires a duplicate-detecting deserializer in mds-core: a custom Visitor / MapAccess implementation that tracks seen keys during parsing and errors or warns on a repeat, replacing the plain serde_json::Value deserialization at lib.rs:1429.

Open design question

Warn, or hard error?

  • Warn matches whatever --set duplicate key silently last-wins — warn? #200 settles on for --set, keeping the two surfaces consistent.
  • Hard error is defensible for a file (unlike repeated CLI flags, a duplicate key in a checked-in JSON file is almost certainly a mistake, and JSON producers rarely emit them intentionally).

These should probably be decided together with #200 so --set and --vars do not end up with divergent behaviour for the same user error.

Blast radius

load_vars_file is pub in mds-core, a published crate, so changing its behaviour is a public-API change. Note that in-repo the only caller is the CLI (build.rs:518) — the napi, WASM, and Python binding crates do not call it — so the in-tree blast radius is small even though the API is public.

Why deferred

Found during design work on #200 and deliberately scoped out of it. #200's fix is a contained CLI-side check; this one requires a core-level deserializer change plus its own warn-vs-error decision, and bundling them would have widened that change materially.

Cross-reference: #200.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cliCLI commands and optionsquestionFurther information is requested

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions