fix(config): say what a rejected value should have been, and prune the group it left behind - #650
Open
filip131311 wants to merge 1 commit into
Open
fix(config): say what a rejected value should have been, and prune the group it left behind#650filip131311 wants to merge 1 commit into
filip131311 wants to merge 1 commit into
Conversation
…e group it left
Setting the one list-valued key to a single path — the natural thing to type —
answered with `Invalid value for config key "ios.additionalDeviceSets".` and
nothing more: no shape, no example, no next step. The schema already carried an
example for every settable key; nothing read it.
A rejected value now says what the key accepts, and where the correction is
mechanical it offers the user's own value back, wrapped and on the scope they
asked for:
Error: Invalid value for config key "ios.additionalDeviceSets": expected an
array of strings.
Did you mean: argent config set ios.additionalDeviceSets '["~/Devices"]' --scope project
Run `argent config list` to see each key's expected value.
The shape comes from the validator itself rather than a per-key string, so the
wording cannot drift from what is enforced. Nothing is coerced on the user's
behalf: the corrected command is shown, not run, so a value is never stored in a
shape they did not ask for. `config list` states the expected value too, which
its description never did — a list-valued key read exactly like a string one.
Unsetting the last key under a group left `{ "ios": {} }` behind, so `unset` did
not restore what `set` had changed — noise in a project config that gets
committed. Deleting a key now takes with it any container the delete emptied,
stopping at the first ancestor that still holds something. The root object always
survives, and a no-op unset still does not touch the file at all, so a group
someone deliberately left empty is never tidied away.
Two tests asserted the old behaviour and now assert the new one.
Fixes #643
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #643.
Before / after
Symptom 1 — the message
The issue names three gaps: no expected type, no example, no pointer to
config list. All three areclosed, plus one the issue didn't ask for and I think is the actual fix.
The suggestion carries the user's own value. A generic
Example: … '["~/DeviceSets/ci"]'wouldhand someone a path they have never heard of and expect them to spot the difference. Where the
correction is mechanical — a single item offered to a list-valued key — the error offers back what
they typed, wrapped, shell-quoted, and on the scope they asked for. Pasting it works; verified
live that it lands in project scope, not global.
Nothing is coerced. The issue floated accepting a bare string and storing it wrapped; I didn't,
because
config setpersists, gets committed, and is read by other machines, so a value silentlystored in a shape the user didn't type is expensive to debug and
coerceCliValuealready has onesilent fallback. Showing the corrected command costs the user one keystroke and keeps the store
honest. If a reviewer prefers coercion, this is the smaller change to build on.
The expected shape comes from the validator, not a per-key string —
asStringArrayis "anarray of strings", so swapping a key's
parseswaps its description with it and the two cannotdrift. A bespoke validator can override via a new optional
expected, and degrades to today's exactmessage if it says nothing.
config liststates it too. The issue noteslistdoesn't close the gap either — itsdescription describes the elements but never says the value is a list. It now shows
value: an array of strings, e.g. ["~/DeviceSets/ci"], andlist --jsoncarries both fields.telemetry.enableddeliberately gets no example:setConfigValuerefuses it before validating(it's managed by
argent telemetry), so a "here's how to set it" hint would point at a command youmay not run. A schema guard test keeps that honest as keys are added — and asserts every example
actually round-trips through its own validator, so an example can never ship that reproduces the
error it exists to fix.
Symptom 2 — the empty group
Deleting a key now removes any container the delete emptied, stopping at the first ancestor that
still holds something.
This is file hygiene, not a behaviour change. I traced every raw reader of the document —
readConfigObject, and telemetry'sconsent.tsandnotice.ts, which each do their own read — andan empty parent is already indistinguishable from an absent one:
getAtPathreturnsundefinedeither way, before
parse, so the merge layer never sees a container at all. The only observabledifference is the bytes on disk, which is exactly the complaint.
Guards worth noting:
{}, never a deleted file..argent/isitself a project-root marker, so removing the file wouldn't restore the prior state anyway, and
updateConfigis a shared publish path with a lock/rename protocol that has no defined meaning forfile removal.
nothing can't materialise a project config and dirty git status; pruning lives inside the mutate
callback, which the fast path returns before reaching. So a group someone deliberately left empty
by hand is never tidied away — pinned by a test asserting the file stays byte-identical.
Two existing tests asserted the old behaviour and now assert the new one —
config-access.test.ts:47andconfig.test.ts:109(the latter isclearRememberedAgent'ssibling-preservation test, so it doubles as proof siblings survive).
Known same-class case, not fixed here
resetFirstRunNotice(packages/telemetry/src/notice.ts) does its own inline delete and stillleaves
{"notices":{}}. It doesn't go throughdeleteAtPath, so it doesn't inherit this. It's aone-line change to route it through the same function — happy to do it here or as a follow-up, but
it pulls the telemetry package into a config PR so I left it out by default.
Verification
Live against the built CLI: the reported case; pasting the suggestion back and confirming it lands in
project scope; unset leaving
{}; a sibling key surviving; a non-array key correctly getting thegeneric example and no bogus "Did you mean";
config listoutput;telemetry.enabledstilldelegating unchanged.
configuration-core 103 passed, argent-cli 285 passed.
🤖 Generated with Claude Code