Refuse config keys that no setting has (closes #419) #443

Merged
clawbot merged 1 commits from issue-419-unknown-config-keys into next 2026-11-11 05:05:25 +01:00
Collaborator

Fixes #419: a key that no setting has was ignored, so a typo in exclude backed up what the user meant to leave out.

  • config.Parse now walks the file as the user wrote it and fails on the first unknown key at any depth, naming its dotted path and line: unknown config key "snapshots.home.exlude" on line 7. It checks before smartconfig fills in ${...} references, so a key typo is reported even when a referenced variable is unset, and references in any value still load.
  • Keys that a ${...} reference brings in, such as a snapshot read with ${YAML:...}, are checked once smartconfig has filled it in. That error names the dotted path without a line, since the key is not in the file.
  • config set refuses a key that no setting has before it writes the file.
  • The key-to-field lookup moved from internal/cli to config.SettingType, which both use.

What the diff does not show:

  • yaml.v3's own strict mode (KnownFields) is not used. Parse decodes a copy re-marshalled by smartconfig, so its line numbers would point into that copy, not the user's file.
  • YAML anchors and merge keys (<<: *base) still load; the keys they bring in are checked where they are written.
  • A top-level env: section still loads, and its keys are not checked: smartconfig reads it to set environment variables. vaultik config set env.FOO bar sets that key like any other value.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/vaultik/issues/419: a key that no setting has was ignored, so a typo in `exclude` backed up what the user meant to leave out. - `config.Parse` now walks the file as the user wrote it and fails on the first unknown key at any depth, naming its dotted path and line: `unknown config key "snapshots.home.exlude" on line 7`. It checks before `smartconfig` fills in `${...}` references, so a key typo is reported even when a referenced variable is unset, and references in any value still load. - Keys that a `${...}` reference brings in, such as a snapshot read with `${YAML:...}`, are checked once `smartconfig` has filled it in. That error names the dotted path without a line, since the key is not in the file. - `config set` refuses a key that no setting has before it writes the file. - The key-to-field lookup moved from `internal/cli` to `config.SettingType`, which both use. What the diff does not show: - `yaml.v3`'s own strict mode (`KnownFields`) is not used. `Parse` decodes a copy re-marshalled by `smartconfig`, so its line numbers would point into that copy, not the user's file. - YAML anchors and merge keys (`<<: *base`) still load; the keys they bring in are checked where they are written. - A top-level `env:` section still loads, and its keys are not checked: `smartconfig` reads it to set environment variables. `vaultik config set env.FOO bar` sets that key like any other value. Model: opus-5-5
clawbot added the needs-review label 2026-11-11 01:38:01 +01:00
clawbot self-assigned this 2026-11-11 01:38:01 +01:00
Author
Collaborator

FAIL: needs-rework

  1. internal/config/config.go Parse (line 303) and checkKeys in internal/config/keys.go: keys are checked only in the file as written. An unknown key inside a map that a ${...} reference fills in still loads silently. For example, snapshots: ${YAML:/etc/vaultik/snaps.yml:snapshots} with exlude: in that file loads with no excludes. s3: ${YAML:...} with bukcet: loads with no bucket. That is the failure the issue describes, and the PR body does not disclose it. Acceptable: loading also fails on an unknown key that a reference brings in, naming its dotted path (without a line, since the key is not in the file). A test covers it with a ${YAML:...} reference.

  2. internal/cli/config_test.go lines 224–228: the doc comment of TestConfigSetKeepsFileThatWouldNotLoad still says that on a config that does not load "only a value of the wrong type is refused". The PR's own compression_lvl case refuses an unknown key on such a config. Acceptable: the comment names both.

  3. README.md lines 314–322, and the same text in the config set help in internal/cli/config.go lines 443–451:

    • The sentence the PR edited, "A config that does not load, such as the one config init writes … is refused.", runs about 70 words, and its verb comes after a long inserted clause.
    • The added sentence "A key that no setting has, such as a misspelled one, is refused." paraphrases "unknown key", the term the error itself uses. The plan on the issue also limits README edits to sentences the fix makes false.
    • Acceptable: short sentences, with the unknown key folded into the corrected sentence. For example: "A config that does not load is checked one setting at a time. Examples are the file config init writes before storage_url is set, and one with a ${ENV:...} reference to a variable not set in this shell. In such a config, an unknown key or a value of the wrong type is refused."

Open point for sneak, not counted as a finding: a top-level env: section is now refused as an unknown key. The config library uses that section to set environment variables before vaultik reads them, VAULTIK_AGE_SECRET_KEY among them. No vaultik document offers it, so I treated the refusal as within the issue.

Model: opus-5-5

**FAIL: `needs-rework`** 1. `internal/config/config.go` `Parse` (line 303) and `checkKeys` in `internal/config/keys.go`: keys are checked only in the file as written. An unknown key inside a map that a `${...}` reference fills in still loads silently. For example, `snapshots: ${YAML:/etc/vaultik/snaps.yml:snapshots}` with `exlude:` in that file loads with no excludes. `s3: ${YAML:...}` with `bukcet:` loads with no bucket. That is the failure the issue describes, and the PR body does not disclose it. Acceptable: loading also fails on an unknown key that a reference brings in, naming its dotted path (without a line, since the key is not in the file). A test covers it with a `${YAML:...}` reference. 2. `internal/cli/config_test.go` lines 224–228: the doc comment of `TestConfigSetKeepsFileThatWouldNotLoad` still says that on a config that does not load "only a value of the wrong type is refused". The PR's own `compression_lvl` case refuses an unknown key on such a config. Acceptable: the comment names both. 3. `README.md` lines 314–322, and the same text in the `config set` help in `internal/cli/config.go` lines 443–451: - The sentence the PR edited, "A config that does not load, such as the one `config init` writes … is refused.", runs about 70 words, and its verb comes after a long inserted clause. - The added sentence "A key that no setting has, such as a misspelled one, is refused." paraphrases "unknown key", the term the error itself uses. The plan on the issue also limits README edits to sentences the fix makes false. - Acceptable: short sentences, with the unknown key folded into the corrected sentence. For example: "A config that does not load is checked one setting at a time. Examples are the file `config init` writes before `storage_url` is set, and one with a `${ENV:...}` reference to a variable not set in this shell. In such a config, an unknown key or a value of the wrong type is refused." Open point for sneak, not counted as a finding: a top-level `env:` section is now refused as an unknown key. The config library uses that section to set environment variables before vaultik reads them, `VAULTIK_AGE_SECRET_KEY` among them. No vaultik document offers it, so I treated the refusal as within the issue. Model: opus-5-5
Author
Collaborator

For the reworker. Fix findings 1, 2 and 3 of #443 (comment) as their "Acceptable" lines say. For finding 3, use the example wording given there, in both the README and the config set help.

  1. The review's open point is settled here: a top-level env: section stays accepted. The config library reads it to set environment variables, so it is a working feature, not a misspelling. Refusing it is outside this issue. Its contents are not checked against vaultik's settings. Add a test that a config with an env: section still loads.

Change nothing else. Run make fmt and make check, push with --force-with-lease, and reply here with one line per point.

For the next reviewer: check these four points against the rework, and anything the rework broke.

Model: opus-5-5

For the reworker. Fix findings 1, 2 and 3 of https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-176684 as their "Acceptable" lines say. For finding 3, use the example wording given there, in both the README and the `config set` help. 4. The review's open point is settled here: a top-level `env:` section stays accepted. The config library reads it to set environment variables, so it is a working feature, not a misspelling. Refusing it is outside this issue. Its contents are not checked against vaultik's settings. Add a test that a config with an `env:` section still loads. Change nothing else. Run `make fmt` and `make check`, push with `--force-with-lease`, and reply here with one line per point. For the next reviewer: check these four points against the rework, and anything the rework broke. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-11-11 01:59:02 +01:00
clawbot force-pushed issue-419-unknown-config-keys from ba343c30e2 to 3a74896114 2026-11-11 02:24:11 +01:00 Compare
Author
Collaborator
  1. Fixed: Parse checks keys again once smartconfig has filled in ${...} references, naming the dotted path without a line; the "from a reference" case in TestParseRefusesUnknownKey uses ${YAML:...}.
  2. Fixed: the comment names both.
  3. Fixed: the example wording, in the README and the config set help; the added sentence is gone from both.
  4. Done: a top-level env: section loads and its keys are not checked; TestParseAcceptsEnvSection covers it.

Judgement call: vaultik config set env.FOO bar is still refused, since env is not a vaultik setting.

Model: opus-5-5

1. Fixed: `Parse` checks keys again once `smartconfig` has filled in `${...}` references, naming the dotted path without a line; the "from a reference" case in `TestParseRefusesUnknownKey` uses `${YAML:...}`. 2. Fixed: the comment names both. 3. Fixed: the example wording, in the README and the `config set` help; the added sentence is gone from both. 4. Done: a top-level `env:` section loads and its keys are not checked; `TestParseAcceptsEnvSection` covers it. Judgement call: `vaultik config set env.FOO bar` is still refused, since `env` is not a vaultik setting. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-11-11 02:28:56 +01:00
Author
Collaborator

FAIL: needs-rework

  1. internal/config/keys.go, checkKeys (lines 75–76) and the merge-key branch of checkMappingKeys (lines 116–131): a merge key whose anchor holds that merge key, such as home: &h with <<: *h inside it, makes the walk recurse without end. vaultik snapshot list and vaultik config set then die with fatal error: stack overflow and a stack dump. On next the same file fails with yaml: anchor 'h' value contains itself. Acceptable: such a file fails to load with an error, not a crash, and a test in internal/config/keys_test.go covers it.

  2. internal/cli/config.go line 49: config set reports an unknown key as no setting has this key: nosuchkey, while loading says unknown config key "nosuchkey" and the README and config set help call it "an unknown key". Acceptable: config set uses the loading error's wording, and the three new cases in internal/cli/config_test.go lines 240–247 expect it.

  3. PR body: 267 words, over the limit of about 250. Its last bullet under "What the diff does not show" describes the README and help change, which the diff shows. Acceptable: that bullet removed and the body under 250 words.

Not counted: the third sentence of the README opening puts an exception in a parenthesis, which the prose rule fails. The plan on #419 limits this change to README sentences the fix makes false, and the README rewrite in #399 is with sneak.

Model: opus-5-5

**FAIL: `needs-rework`** 1. `internal/config/keys.go`, `checkKeys` (lines 75–76) and the merge-key branch of `checkMappingKeys` (lines 116–131): a merge key whose anchor holds that merge key, such as `home: &h` with `<<: *h` inside it, makes the walk recurse without end. `vaultik snapshot list` and `vaultik config set` then die with `fatal error: stack overflow` and a stack dump. On `next` the same file fails with `yaml: anchor 'h' value contains itself`. Acceptable: such a file fails to load with an error, not a crash, and a test in `internal/config/keys_test.go` covers it. 2. `internal/cli/config.go` line 49: `config set` reports an unknown key as `no setting has this key: nosuchkey`, while loading says `unknown config key "nosuchkey"` and the README and `config set` help call it "an unknown key". Acceptable: `config set` uses the loading error's wording, and the three new cases in `internal/cli/config_test.go` lines 240–247 expect it. 3. PR body: 267 words, over the limit of about 250. Its last bullet under "What the diff does not show" describes the README and help change, which the diff shows. Acceptable: that bullet removed and the body under 250 words. Not counted: the third sentence of the README opening puts an exception in a parenthesis, which the prose rule fails. The plan on https://git.eeqj.de/sneak/vaultik/issues/419 limits this change to README sentences the fix makes false, and the README rewrite in https://git.eeqj.de/sneak/vaultik/pulls/399 is with sneak. Model: opus-5-5
Author
Collaborator

For the reworker: fix the three findings of #443 (comment) as their "Acceptable" lines say, and change nothing else. Rebase onto current origin/next, run make fmt and make check, push with --force-with-lease, and reply here with one line per finding.

For the next reviewer: check those three findings against the rework, and anything the rework broke.

Model: opus-5-5

For the reworker: fix the three findings of https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-176911 as their "Acceptable" lines say, and change nothing else. Rebase onto current `origin/next`, run `make fmt` and `make check`, push with `--force-with-lease`, and reply here with one line per finding. For the next reviewer: check those three findings against the rework, and anything the rework broke. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-11-11 02:42:14 +01:00
clawbot force-pushed issue-419-unknown-config-keys from 3a74896114 to abb0447c6b 2026-11-11 02:54:24 +01:00 Compare
Author
Collaborator
  1. Fixed: the key walk tracks the aliases it is inside, so such a file fails to load with an error such as anchor contains itself: "h" on line 6. TestParseRefusesAnchorThatContainsItself in internal/config/keys_test.go covers it.
  2. Fixed: config.SettingType returns unknown config key "nosuchkey", which config set reports; the three cases expect it.
  3. Fixed: the bullet is removed and the body is under 250 words.

Judgement call: the walk tracks aliases itself rather than relying on the YAML decoder's check, which skips the value of a key that a merge overrides; an anchor that merges itself inside such a value would still overflow the stack.

Model: opus-5-5

1. Fixed: the key walk tracks the aliases it is inside, so such a file fails to load with an error such as `anchor contains itself: "h" on line 6`. `TestParseRefusesAnchorThatContainsItself` in `internal/config/keys_test.go` covers it. 2. Fixed: `config.SettingType` returns `unknown config key "nosuchkey"`, which `config set` reports; the three cases expect it. 3. Fixed: the bullet is removed and the body is under 250 words. Judgement call: the walk tracks aliases itself rather than relying on the YAML decoder's check, which skips the value of a key that a merge overrides; an anchor that merges itself inside such a value would still overflow the stack. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-11-11 03:15:38 +01:00
Author
Collaborator

FAIL: needs-rework

  1. internal/config/keys.go, the alias case of checkKeys (line 82) and the merge-key branch of checkMappingKeys (line 133): the key walk follows an alias again every time it meets one, so merge keys that fan out make the walk exponential. Take a file of about 600 bytes in which each of eight snapshots has a merge key listing the snapshot before it ten times. With it, vaultik snapshot list and vaultik config set run for minutes. On next the same file fails at once with yaml: document contains excessive aliasing, the YAML library's own limit, which the walk now runs ahead of. Acceptable: such a file fails to load with an error as quickly as on next, for example by checking each anchored map once per setting type instead of once per alias. A test in internal/config/keys_test.go covers it.

  2. internal/cli/config.go line 486, the config.SettingType call in writeConfigSet: vaultik config set env.NAME value is refused with unknown config key "env.NAME", though loading accepts the same key in a top-level env: section. On next it sets the key. Point 4 of #443 (comment) keeps that section working and says its contents are not checked against vaultik's settings. Acceptable: config set treats a key under the top-level env as loading does and sets it, and a case in internal/cli/config_test.go covers config set env.NAME value.

Judgement call: finding 2 overrides the reworker's disclosed judgement call in #443 (comment).

Model: opus-5-5

**FAIL: `needs-rework`** 1. `internal/config/keys.go`, the alias case of `checkKeys` (line 82) and the merge-key branch of `checkMappingKeys` (line 133): the key walk follows an alias again every time it meets one, so merge keys that fan out make the walk exponential. Take a file of about 600 bytes in which each of eight snapshots has a merge key listing the snapshot before it ten times. With it, `vaultik snapshot list` and `vaultik config set` run for minutes. On `next` the same file fails at once with `yaml: document contains excessive aliasing`, the YAML library's own limit, which the walk now runs ahead of. Acceptable: such a file fails to load with an error as quickly as on `next`, for example by checking each anchored map once per setting type instead of once per alias. A test in `internal/config/keys_test.go` covers it. 2. `internal/cli/config.go` line 486, the `config.SettingType` call in `writeConfigSet`: `vaultik config set env.NAME value` is refused with `unknown config key "env.NAME"`, though loading accepts the same key in a top-level `env:` section. On `next` it sets the key. Point 4 of https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-176685 keeps that section working and says its contents are not checked against vaultik's settings. Acceptable: `config set` treats a key under the top-level `env` as loading does and sets it, and a case in `internal/cli/config_test.go` covers `config set env.NAME value`. Judgement call: finding 2 overrides the reworker's disclosed judgement call in https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-176789. Model: opus-5-5
Author
Collaborator

For the reworker. This is the third failed review, and this comment ends the loop. Make exactly these two changes. Change nothing else, including tests and comments the review did not name.

  1. Finding 1, aliases followed again and again (#443 (comment)). In internal/config/keys.go, give the walk a second set beside following: the pairs of alias target node and struct type it has already checked completely. In the yaml.AliasNode case, keep the existing following check and its error as they are. Then, if the pair (node.Alias, typ) is already in the done set, return nil. After checkKeys(node.Alias, ...) returns without error, add the pair. Pass the set through checkKeys, checkMappingKeys and checkFilledKeys the same way following is passed. Each anchored map is then checked once per setting type.
    • Test in internal/config/keys_test.go: the review's file of eight snapshots, each with a merge key listing the snapshot before it ten times. Parse of it returns an error, and the test itself takes no time limit. Without the fix it runs past the package's timeout.
  2. Finding 2, config set env.NAME. In writeConfigSet in internal/cli/config.go, skip the config.SettingType check when the first part of the key is env. Such a key is then set like any other value. Add one case in internal/cli/config_test.go: config set env.NAME value on a config that loads writes env: {NAME: value}.

Rebase onto current origin/next, run make fmt and make check, push with --force-with-lease, and reply here with one line per change.

For the next reviewer. Your verdict covers only these two changes, done as written, and that make check passes. Three reviews have covered the rest. List anything else you notice under "For the manager", one line each. It does not fail the PR.

Model: opus-5-5

For the reworker. This is the third failed review, and this comment ends the loop. Make exactly these two changes. Change nothing else, including tests and comments the review did not name. 1. **Finding 1, aliases followed again and again** (https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-177184). In `internal/config/keys.go`, give the walk a second set beside `following`: the pairs of alias target node and struct type it has already checked completely. In the `yaml.AliasNode` case, keep the existing `following` check and its error as they are. Then, if the pair (`node.Alias`, `typ`) is already in the done set, return nil. After `checkKeys(node.Alias, ...)` returns without error, add the pair. Pass the set through `checkKeys`, `checkMappingKeys` and `checkFilledKeys` the same way `following` is passed. Each anchored map is then checked once per setting type. - Test in `internal/config/keys_test.go`: the review's file of eight snapshots, each with a merge key listing the snapshot before it ten times. `Parse` of it returns an error, and the test itself takes no time limit. Without the fix it runs past the package's timeout. 2. **Finding 2, `config set env.NAME`.** In `writeConfigSet` in `internal/cli/config.go`, skip the `config.SettingType` check when the first part of the key is `env`. Such a key is then set like any other value. Add one case in `internal/cli/config_test.go`: `config set env.NAME value` on a config that loads writes `env: {NAME: value}`. Rebase onto current `origin/next`, run `make fmt` and `make check`, push with `--force-with-lease`, and reply here with one line per change. **For the next reviewer.** Your verdict covers only these two changes, done as written, and that `make check` passes. Three reviews have covered the rest. List anything else you notice under "For the manager", one line each. It does not fail the PR. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-11-11 03:53:27 +01:00
clawbot force-pushed issue-419-unknown-config-keys from abb0447c6b to c40ff647c3 2026-11-11 04:02:11 +01:00 Compare
clawbot added 1 commit 2026-11-11 04:17:09 +01:00
Refuse config keys that no setting has (closes #419)
check / check (push) Successful in 9m24s
26914097a1
config.Parse walks the file as written and fails on the first key that
matches no setting, naming its dotted path and line, so a typo in
exclude or prune_grace_period no longer falls back silently. Keys that a
${...} reference brings in are checked once smartconfig has filled it
in, and named without a line. YAML merge keys and a top-level env
section, which smartconfig reads, still load; an anchor that contains
itself is an error, and each anchored map is checked once per type.
config set refuses such a key before writing, with the same error, and
sets a key under env like any other value. The key-to-field lookup
moves from internal/cli to config.SettingType so both use it.

Model: opus-5-5
clawbot force-pushed issue-419-unknown-config-keys from c40ff647c3 to 26914097a1 2026-11-11 04:17:09 +01:00 Compare
Author
Collaborator
  1. Done: an alias to an anchored node already checked completely for that type is not followed again; TestParseRefusesExcessiveAliasing in internal/config/keys_test.go covers it with the eight-snapshot file.
  2. Done: writeConfigSet skips the config.SettingType check when the key's first part is env; TestConfigSetWritesEnvSection in internal/cli/config_test.go covers config set env.NAME value.

Judgement call: envSection is now the exported config.EnvSection, so internal/cli uses the same constant.
The commit message and the PR body's config set env.FOO bar sentence are updated to match change 2.

Model: opus-5-5

1. Done: an alias to an anchored node already checked completely for that type is not followed again; `TestParseRefusesExcessiveAliasing` in `internal/config/keys_test.go` covers it with the eight-snapshot file. 2. Done: `writeConfigSet` skips the `config.SettingType` check when the key's first part is `env`; `TestConfigSetWritesEnvSection` in `internal/cli/config_test.go` covers `config set env.NAME value`. Judgement call: `envSection` is now the exported `config.EnvSection`, so `internal/cli` uses the same constant. The commit message and the PR body's `config set env.FOO bar` sentence are updated to match change 2. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-11-11 04:29:07 +01:00
Author
Collaborator

PASS: both changes in #443 (comment) are done as written: each anchored map is checked once per setting type, so the fan-out file fails at once with the YAML library's error, and config set env.NAME value sets the key.

Model: opus-5-5

**PASS**: both changes in https://git.eeqj.de/sneak/vaultik/pulls/443#issuecomment-177188 are done as written: each anchored map is checked once per setting type, so the fan-out file fails at once with the YAML library's error, and `config set env.NAME value` sets the key. Model: opus-5-5
clawbot merged commit d8d13d1c8b into next 2026-11-11 05:05:25 +01:00
clawbot deleted branch issue-419-unknown-config-keys 2026-11-11 05:05:25 +01:00
Sign in to join this conversation.