feat(config): warn about avocado.yaml keys the cli ignores - #286
lee-reinhardt wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved moderate schema and linter coverage gaps can allow ignored or valid configuration keys to be misreported.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 2
Open (2)
What changed in this PR
Adds schema-backed warnings for ignored avocado.yaml keys, with suggestions, JSON output integration, and editor tooling support.
Changes:
- Adds recursive configuration linting and deprecation warnings.
- Embeds the configuration schema and updates the default template.
- Adds warning propagation to
config show --output json. - Adds
strsimas a direct dependency.
| File | Summary |
|---|---|
src/utils/mod.rs |
Registers the config linter module. |
src/utils/config.rs |
Runs linting during configuration loads. |
src/utils/config_lint.rs |
Implements schema-based analysis and tests. |
src/commands/config_show.rs |
Includes warnings in JSON output. |
schemas/avocado-config.json |
Defines configuration fields and warning metadata. |
configs/default.yaml |
Adds YAML language-server schema metadata. |
Cargo.toml |
Adds strsim. |
Cargo.lock |
Records the dependency update. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The config structs drop fields they don't declare, and much of the file
(extensions, overlays, users and groups, per-target overrides) is only
ever read as raw YAML, so a misspelled key did nothing and said nothing.
In rootfs, initramfs, kernel and permissions a typo was worse: a section
whose keys were all misspelled parsed as a named entry, quietly changing
what the file meant.
The config schema moves here from the docs repo, rebuilt from what the
code actually reads, and the cli walks the user's own file against it.
Every key it ignores gets one warning per run on stderr, with a "did you
mean" suggestion when a known key is close. Renamed keys (ext, runtime,
sdk.dependencies, sysext/confext booleans) and keys that parse but do
nothing say so. It warns and never fails, so a file an older cli
accepted still builds.
Keys with {{ }} templates are treated as names, a top-level key the file
reads back through {{ config.<key> }} is allowed, and a bare target-name
override is recognised by its shape, since its target may only be known
from the command line or after interpolation.
`config show --output json` carries the warnings in a `warnings` array,
present only when there are any, so clean output is unchanged. The init
template points editors at the published schema.
A test fails when a typed config struct accepts a field the schema
doesn't describe. Keys read raw are not covered by it.
924eadf to
2f97771
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Five unresolved moderate issues affect schema coverage and false-negative or false-positive linting behavior.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
Resolved since last review (2)
jetm
left a comment
There was a problem hiding this comment.
Nothing blocking. This is careful work: the shape-inference in pick_branch correctly mirrors how the CLI itself disambiguates anyOf branches (tag match, then singleton-by-claimed-field, then named-entry), the sets_no_field/sets_a_field heuristics for named-entry-vs-misspelled-singleton are well-targeted, and schema_describes_every_typed_config_field's trick of extracting serde's own field names through a custom Deserializer is a genuinely good way to keep the schema and the real structs from drifting apart. I traced the JSON-mode double-report question (loading prints to stderr, but --output json also rebuilds the warnings for the payload) and it's handled correctly: print_stderr_notice checks json_requested_on_command_line() and stays quiet in that mode, so there's no double emission.
Two things worth knowing, neither blocking. resolve()'s $ref follower has no cycle guard - a future recursive schema definition would hang it in an infinite loop rather than error, though I checked and the current avocado-config.json has no cycles today. And a handful of other config-reading helpers (get_runtime_names, get_merged_section_with_board) read the file directly rather than going through Config::load/load_composed_with_board, so they don't call warn_ignored_keys_once themselves - I checked runtime clean and ext build, the two live callers reaching those helpers, and both already call load_composed/load_composed_with_board earlier in the same command, which fires the warning first, so this isn't a live gap in either path today.

Problem
A misspelled key in
avocado.yamldoes nothing and says nothing. A user evaluating Avocado hit this withextentions:in a runtime (the runtime came out with zero extensions) andpermissions: { user: ... }, and asked for warnings and a usable schema.The config structs use serde's default of dropping unknown fields, and large parts of the file are never typed at all:
extensions, overlays, users and groups, and per-target override blocks are read as raw YAML, so a key nobody looks up is simply skipped. Inrootfs,initramfs,kernelandpermissionsit's worse: a section whose keys are all misspelled has no recognised field, so it parses as a named entry, silently changing what the file means.The published JSON schema didn't help. It lived in the docs repo, maintained by hand, and had drifted: no
rootfs,initramfs,permissions,reposorconnect, most extension keys missing, an open top level, and a$idthat returns 404.Change
The schema moves here (
schemas/avocado-config.json), rebuilt from a key-by-key inventory of what the code reads, typed and raw, at the level it reads it. Every description was checked against the code. Its$idis the docs URL; the docs site will publish this copy (separate PR).The CLI warns about every key it ignores (
src/utils/config_lint.rs), walking the user's own file against the embedded schema:Config::load,load_composed_with_board) once per file per process, keyed on the canonical path, since commands load the same file many times.--output json, whereconfig show --output jsoncarries the messages in awarningsarray instead. The array is present only when there are warnings, so clean output is byte-identical.ext,runtime,sdk.dependencies, thesysext/confextbooleans) and keys that parse but do nothing (sdk.host_uid, a user'sdisabled, a group'spassword) get a specific message from anx-avocado-warningin the schema.{{ }}are treated as names. A top-level key the file reads back through{{ config.<key> }}is allowed. A bare target-name override is accepted where the CLI resolves one, recognised by shape (it sets the block's own fields), since the target may only be known from--targetor after interpolation.sourcecounts as a path source only withtype: path.The
avocado inittemplate starts with ayaml-language-serverschema comment, so VS Code with the YAML extension gets autocomplete, hover docs and validation.strsimbecomes a direct dependency for the suggestions; it was already in the lock file through clap.Keeping the schema current
schema_describes_every_typed_config_fieldfails when a typed config struct accepts a field (or alias) the schema doesn't describe, naming the missing pointer. Keys read raw are not covered: a PR that adds a raw read has to add the key to the schema by hand. Typing the raw sections, starting withextensions, would bring them under the same test.Tests
config_lint: clean configs, suggestions, renamed and no-effect keys, the named-entry trap, taggedsourcebranches, templated keys and tags, bare-target overrides, interpolation variables, sequence paths, the drift guard (confirmed by deleting a field and an alias from the schema), and a check that the repo's own configs and fixtures produce no warnings.avocado.yamlfiles in avocado-linux/references and avocado-os produce zero warnings, run throughavocado config show.cargo fmt --check,cargo clippy --all-targets --all-features -- -D warningsand all integration tests pass. On macOS, 5 existing lib tests instamps.rsandext/build.rsfail because they shell out to GNUsed/sha256 tools; they don't touch config loading.Not in this PR
--output json, so a config that references an unset env var already produces invalid JSON fromconfig show.