Skip to content

fix(cli): <plugin> config set writes the value instead of printing - #9

Merged
ValentinFigue merged 2 commits into
mainfrom
fix/plugin-config-set
Jul 31, 2026
Merged

fix(cli): <plugin> config set writes the value instead of printing#9
ValentinFigue merged 2 commits into
mainfrom
fix/plugin-config-set

Conversation

@ValentinFigue

Copy link
Copy Markdown
Owner

Found by the documentation audit in #8, and deliberately left out of it because it needs a behaviour change and a test rather than a doc edit.

The bug

config)  cmd_config show "$(_mf "$_PLUGIN" config_section …)" "$@" ;;

Everything after config went to show as trailing arguments, which ignores them. So:

$ temper config set auto_nudge_lines 300
[temper]
  enabled              true      default
  auto_nudge_lines     200       default        ← unchanged
$ echo $?
0

It printed the config and exited 0 having set nothing. cairn config reset local did the same. A silent success, in the CLI of a tool whose whole argument is that reporting success while doing nothing is the dangerous failure.

Worth noting the sibling *) case does reject unknown subcommands — only config swallowed.

The fix

Forward the subcommand, and namespace the key by looking up the section the manifest schema declares for it. That lookup is the point of having a shim: you should not need to know that trailers lives in [git] rather than [cairn].

temper config set auto_nudge_lines 300     → [temper] auto_nudge_lines
cairn  config set trailers Signed-off-by   → [git] trailers      (resolved from the schema)
cairn  config set pr.base develop          → [cairn] pr.base     (dots intact)

_schema_all already emits section|key|plugin, so _plugin_section_for is a lookup rather than a second place to encode which key lives where.

Three details that decide whether this is correct, each with an assertion:

  • Prefixing is what makes dotted keys work. _split_key splits on the first dot, so a bare pr.base would resolve to a section called pr. There is a test that no [pr] section is invented.
  • An already-qualified key is passed through. Otherwise temper config set temper.auto_nudge_lines 300 writes temper.temper.auto_nudge_lines.
  • doctor, path and edit are not section-scoped, so they forward unchanged.

Everything else comes free — trust re-hashing, global/local scope parsing, the unknown-key warning — because it lives in cmd_config, which is now actually reached.

An unrecognised subcommand exits 1 and names the real ones.

Also

The trailers doc string suggested Co-Authored-By. That string is written as a comment into every config file the tool generates, so it was quietly recommending the one trailer this project's own rules forbid. Now Signed-off-by.

Verification

16 new assertions in tests/test_cli.sh, the regression first:

✓ temper config set exits 0
✓ …and actually writes the value          ← the regression
✓ …naming the resolved section.key
✓ a key declared under [git] lands in [git]
✓ a dotted key keeps its dots
✓ a dotted key does not invent a [pr] section
✓ an already-qualified key is not prefixed twice
✓ get reads the value back
✓ unset removes the key
✓ the global scope argument still works
✓ bare temper config still shows
✓ an unknown config subcommand exits 1
✓ set with no key exits 1

726 assertions across 10 files pass.

Docs updated in the same change: the three plugin READMEs said "the plugin shim's config only shows", which was true when written and is not now, and the CLI reference gains the equivalence.

The plugin shim's `config` case forwarded everything to `cmd_config show` as
trailing arguments, which ignores them. So `temper config set auto_nudge_lines
300` printed the config and exited 0 having set nothing, and `cairn config
reset local` did the same. A silent success, in the CLI of a tool whose whole
argument is that silent successes are the dangerous failure.

The shim now forwards the subcommand and namespaces the key by looking up the
section the manifest schema declares for it — which is the point of having a
shim: you should not need to know that `trailers` lives in [git] rather than
[cairn].

  temper config set auto_nudge_lines 300     → [temper] auto_nudge_lines
  cairn  config set trailers Signed-off-by   → [git] trailers
  cairn  config set pr.base develop          → [cairn] pr.base, dots intact

Prefixing is also what makes dotted keys work: _split_key splits on the first
dot, so a bare `pr.base` would resolve to a section called `pr`. A key given
already qualified is passed through, or `temper config set
temper.auto_nudge_lines` would write temper.temper.auto_nudge_lines.

get, unset and explain are namespaced the same way. doctor, path and edit pass
through — they answer for the whole file, not a section. An unrecognised
subcommand exits 1 and names the real ones. Trust re-hashing, scope parsing and
the unknown-key warning all come free, because they live in cmd_config, which
is now actually reached.

Also: the `trailers` doc string suggested Co-Authored-By, and that text gets
written into every config file the tool generates.

16 new assertions in tests/test_cli.sh, the regression first.
_plugin_section_for piped _schema_all into awk with `exit` after the first
match, closing the pipe while the writer was still going. Bash on Linux reports
the EPIPE as `printf: write error: Broken pipe` into whatever the caller
captured, so `temper config get` returned the value with an error message glued
to it.

This is the same bug the file already carries a warning about, three lines
above the function I added — "No `exit` in the awk". I wrote one anyway.
_schema_plugin_for's `!f { print; f = 1 }` is the pattern; use it.

Six stderr assertions added, following the precedent in test_config.sh: macOS
dies from SIGPIPE silently, so a value comparison does not reliably catch this
and it has to be asserted directly. That is why it passed locally and failed on
both CI platforms.
@ValentinFigue
ValentinFigue merged commit f1e6a58 into main Jul 31, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant