Skip to content

fix(config): report config values that cannot be applied to their flag - #1090

Merged
JonJagger merged 9 commits into
mainfrom
fix-silent-exit-on-bad-config-value
Aug 12, 2026
Merged

JonJagger merged 9 commits into
mainfrom
fix-silent-exit-on-bad-config-value

Conversation

@JonJagger

Copy link
Copy Markdown
Contributor

Closes the silent-exit half of #1086.

A config file value that fails to apply to its flag was reported with logger.Error inside bindFlags. That is
Fatalf, and the logger's error stream is not wired up there, so the process exited 1 with no output at all.

bindFlags now returns the failure and initialize propagates it. The message names the flag, the config file, and
the format pflag expected.

The YAML list/map handling in #1086 is deliberately not addressed here.

Stacked on #1087's branch; rebase onto main once that merges.

@claude

claude Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @JonJagger's task in 1m 56s —— View job


Claude PR Review

  • Gather context (read changed files, diff vs origin/main)
  • Static analysis (go build, go vet)
  • Review code quality, edge cases, bugs, tests
  • Post review

Verdict: Looks good to merge. The core fix is correct and the code is unusually well-documented for why it does what it does.

Static analysis

  • go build ./... — ✅ clean
  • go vet ./cmd/kosli/ — ✅ clean
  • No dependency changes (errors/os are already imported).

The fix is sound

bindFlags now returns its error via errors.Join instead of the old dead logger.Error-inside-a-closure path, and initialize propagates it (root.go:544-546). Previously the failure was routed through logger.Error (a Fatalf) on an error stream that isn't wired up yet, so the process exited 1 with no output — the silent exit from #1086. That's genuinely fixed, and the message now names the flag, the source, and the format pflag expected (root.go:641-643).

Previous review comments — all addressed ✅

Tracking the earlier bot-review threads on this PR, each has been resolved in later commits:

  • Last-error-wins → now accumulates with errors.Join, and TestAllConfigValuesThatCannotBeAppliedAreReported pins that both bad keys are reported.
  • Source misattribution → configValueSource (root.go:578-584) mirrors viper's env-vs-file precedence, with TestUnappliableValueFromEnvNamesTheEnvVar covering the env path.
  • Stale/contradictory doc comment + orphaned fragment → reworded to describe what the test pins (root_test.go:137-141).
  • Config-file source not asserted → root_test.go:148-149 now asserts the file name.
  • Duplicated Require().Error → dropped.

Nice touches

  • The configValueSource doc comment (root.go:565-577) explicitly calls out the invariant that keeps its manual precedence mirror honest (AllowEmptyEnv(false), caller reached only when viper supplied a value) and warns what to change if AutomaticEnv config drifts. That's exactly the kind of comment that prevents a future silent regression.
  • The empty-env-var-vs-encrypted-token reasoning (root.go:596-604) is a subtle correctness point (an empty KOSLI_API_TOKEN must not skip decryption of a config-file token) and it's well guarded.

Minor observations (non-blocking)

  1. Test failure trigger is implicit. invalid-flag-value.yaml triggers the error because link: is a YAML map, so fmt.Sprintf("%v", val) renders map[docs:...] which --link can't parse. That's fine and intentional (the YAML map handling is explicitly deferred per the PR description), but the test's red state quietly depends on that rendering. A one-line comment in the fixture, or switching link to a plainly-unparseable scalar, would make the intent self-evident. two-invalid-flag-values.yaml is clearer since max-api-retries: not-a-number is an obvious int-parse failure.
  2. configValueSource scope. It distinguishes config-file vs env only — if a value ever originated from v.Set/an alias it would be attributed to the config file. Not reachable through today's binding, and the doc comment scopes this correctly, so just noting it.

Overall a tight, well-reasoned fix with good test coverage of both source-attribution paths. 👍

Comment thread cmd/kosli/root.go Outdated
Comment thread cmd/kosli/root_test.go Outdated
JonJagger added a commit that referenced this pull request Aug 12, 2026
  Two problems with the failure reported when a value cannot be applied to its
  flag, both raised in review of #1090.

  The failures were collected in a single variable inside a VisitAll closure, so
  each one overwrote the last and only the final flag was ever named. A user who
  fixed the reported key just met the next one on the following run. They are
  joined now, so one run reports them all.

  The message also named the config file unconditionally, but viper takes a value
  from the environment when the bound variable holds one. A malformed KOSLI_LINK
  produced an error blaming the config file, sending the user hunting through a
  file that did not contain the offending value. The source is now resolved
  before the message is built, so it names either the variable or the file.
Comment thread cmd/kosli/root_test.go Outdated
Comment thread cmd/kosli/root_test.go Outdated
Base automatically changed from fix-empty-kosli-env-vars-treated-as-set to main August 12, 2026 13:16
JonJagger and others added 6 commits August 12, 2026 14:19
  A config file value that fails to apply to its flag was reported with
  logger.Error from inside bindFlags. That is Fatalf, and the logger's error
  stream is not wired up at that point, so the message went nowhere: the
  process exited 1 with nothing on stdout or stderr at all.

  A bad config value is user input, so bindFlags now returns it as an error and
  initialize propagates it. The message names the flag, the config file, and the
  format pflag expected.
  Two problems with the failure reported when a value cannot be applied to its
  flag, both raised in review of #1090.

  The failures were collected in a single variable inside a VisitAll closure, so
  each one overwrote the last and only the final flag was ever named. A user who
  fixed the reported key just met the next one on the following run. They are
  joined now, so one run reports them all.

  The message also named the config file unconditionally, but viper takes a value
  from the environment when the bound variable holds one. A malformed KOSLI_LINK
  produced an error blaming the config file, sending the user hunting through a
  file that did not contain the offending value. The source is now resolved
  before the message is built, so it names either the variable or the file.
…fixed

  The comment described the logging path in the present tense, but that path is
  what this branch removes, so it contradicted the code directly beneath it. It
  also carried a root.go line reference that the same change had already made
  stale.

  It now states why the failure is returned rather than logged, which is the
  constraint a future reader needs, and cites no line numbers.
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
  configValueSource decides whether a value came from the environment or the
  config file by re-checking the environment, because viper exposes no query for
  which source won: InConfig reports that a key exists in the file, not that the
  file took precedence. That makes it a parallel implementation of viper's rules,
  which is fine while the rules agree but silent if they drift.

  The docstring now says so, and names the specific change that would break it:
  enabling AllowEmptyEnv lets an empty variable win over the config file, which
  this function would still attribute to the file.
@JonJagger
JonJagger force-pushed the fix-silent-exit-on-bad-config-value branch from ad95aa4 to 8c0c4dc Compare August 12, 2026 13:19
  configValueSource already warns that it mirrors viper's precedence, but that
  warning only reaches someone who opens the helper. The likelier route to
  breaking it is editing the binding setup - adding a key replacer, changing the
  prefix, enabling AllowEmptyEnv - and nothing there pointed back.

  The AutomaticEnv call now names the coupling and why it is worth care: no test
  fails when the two drift apart.
Comment thread cmd/kosli/root_test.go
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Comment thread cmd/kosli/root_test.go Outdated
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
@JonJagger
JonJagger merged commit 2ee6268 into main Aug 12, 2026
20 checks passed
@JonJagger
JonJagger deleted the fix-silent-exit-on-bad-config-value branch August 12, 2026 14:15
social4hyq pushed a commit to social4hyq/homebrew-core that referenced this pull request Sep 20, 2026
kosli-cli 2.36.6

Created-by: HarmonybrewBot
Commit-by: HarmonybrewBot
Merged-by: HarmonybrewBot
Description: Created by `brew bump`

---

Created with `brew bump-formula-pr`.<details>
  <summary>release notes</summary>
  <pre># Bug fixes
- Empty `--attachments` or `--template` elements (e.g. from unset shell variables) are now rejected with a clear error instead of being silently dropped.
- An empty `KOSLI_CONFIG_FILE` environment variable no longer suppresses config file loading; it is treated as unset and falls back to the default.
- An empty `KOSLI_API_TOKEN` environment variable no longer skips decryption of a config-file token.
- A boolean flag given an empty value (e.g. `--compliant ""` or `--new-compliance-status ""`) is now rejected with a clear error naming the flag, preventing the opposite compliance verdict from being recorded silently.
- Config file or environment values that cannot be applied to a flag now produce an error naming the flag and its source, instead of silently failing.

## What's Changed
* fix(attest): always serialise commits in pull request attestations by @dangrondahl in kosli-dev/cli#1083
* fix(docs): correct invalid regex in snapshot ecs help examples by @dangrondahl in kosli-dev/cli#1080
* fix(config): treat empty KOSLI_* env vars as unset, not as set by @JonJagger in kosli-dev/cli#1089
* fix(config): report config values that cannot be applied to their flag by @JonJagger in kosli-dev/cli#1090
* fix(flags): reject an empty element in a multi-value flag by @JonJagger in kosli-dev/cli#1092
* fix(flags): reject an empty value written after a boolean flag by @JonJagger in kosli-dev/cli#1091
* chore(deps): bump the github-actions-dependencies group with 3 updates by @dependabot[bot] in kosli-dev/cli#1084
* chore(deps): bump the go-dependencies group with 9 updates by @dependabot[bot] in kosli-dev/cli#1085
* chore(deps): bump anthropics/claude-code-action from 1.0.189 to 1.0.191 in the github-actions-dependencies group by @dependabot[bot] in kosli-dev/cli#1094
* chore(deps): bump the go-dependencies group with 7 updates by @dependabot[bot] in kosli-dev/cli#1095
* chore(deps): bump google.golang.org/protobuf from 1.36.12-0.20260120151049-f2248ac996af to 1.36.12 by @dependabot[bot] in kosli-dev/cli#1096


**Full Changelog**: https://github.com/kosli-dev/cli/compare/v2.36.5...v2.36.6</pre>
  <p>View the full release notes at <a href="https://github.com/kosli-dev/cli/releases/tag/v2.36.6">https://github.com/kosli-dev/cli/releases/tag/v2.36.6</a>.</p>
</details>
<hr>

See merge request: Harmonybrew/homebrew-core!16566
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.

2 participants