Skip to content

fix(cli): accept --config and --log-level before the subcommand - #776

Merged
CybotTM merged 2 commits into
mainfrom
fix/global-flags-before-subcommand
Aug 2, 2026
Merged

fix(cli): accept --config and --log-level before the subcommand#776
CybotTM merged 2 commits into
mainfrom
fix/global-flags-before-subcommand

Conversation

@CybotTM

@CybotTM CybotTM commented Aug 2, 2026

Copy link
Copy Markdown
Member

Closes #772.

The flags looked global and were not

--log-level and --config are pre-parsed out of argv before the logger exists, so they behave globally — but the top-level parser declared neither:

$ ofelia --config /etc/ofelia/config.ini validate
unknown flag `config'

$ ofelia validate --config /etc/ofelia/config.ini
(works)

Nothing about --config suggests it only exists after the subcommand, and it is the flag that decides which file every subcommand reads.

The half that a "does it parse" fix would have missed

Declaring the pair on the parser stops the rejection — but the path still did not arrive. daemon, validate and config-show each carried their own --config with default:"/etc/ofelia/config.ini", and go-flags applies that default during parsing, overwriting the pre-parsed value the command was constructed with.

So after the obvious fix, this happened:

$ ofelia --config=/tmp/mine.ini validate
failed to load config file "/etc/ofelia/config.ini"

The flag was accepted and then ignored. The default now lives once, on the global option; without the tag on the subcommands go-flags leaves the field alone when the flag is absent, so the pre-parsed value survives. When the flag is given after the subcommand the pre-parser has already seen it too, so both positions agree.

Verified against the binary

invocation before after
--config=<good> validate unknown flag 0
validate --config=<good> 0 0
--log-level debug version unknown flag 0
--config=<missing> validate unknown flag 1
validate --config=<missing> 1 1
validate with no flag uses /etc/ofelia/config.ini unchanged

Tests

Two e2e tests, because the two halves fail differently: one that neither position is rejected, and one that the requested path actually reaches the command. The second would still have failed after the parser-only change — it asserts the error names the config that was asked for, not the compiled-in default.

Test plan

  • go test ./... — green
  • go test -race -tags=e2e ./e2e/... — green
  • golangci-lint run incl. --build-tags="e2e unix" — 0 issues
  • lefthook run pre-push — exit 0

Closes #772.

Both flags are pre-parsed out of argv before the logger exists, so they
behave globally — but the top-level parser declared neither, and writing
them where a user naturally would was rejected:

    $ ofelia --config /etc/ofelia/config.ini validate
    unknown flag `config'

    $ ofelia validate --config /etc/ofelia/config.ini
    (works)

Nothing about `--config` suggests it only exists after the subcommand,
and it is the flag that decides which file every subcommand reads.

Two changes, and the second is the one that matters. Declaring the pair
on the parser stops the rejection, but the path still did not arrive:
daemon, validate and config-show each carried their own `--config` with
`default:"/etc/ofelia/config.ini"`, and go-flags applies that default
during parsing, overwriting the pre-parsed value the command was built
with. `ofelia --config=x validate` then read /etc/ofelia/config.ini
while reporting success on a file it had never opened.

The default now lives once, on the global option. Without the tag on the
subcommands, go-flags leaves the field alone when the flag is absent, so
the value from the pre-parse survives — and when the flag is given after
the subcommand the pre-parser has already seen it too, so both positions
agree.

The e2e tests cover both halves: that neither position is rejected, and
that the requested path actually reaches the command. The second would
have failed on the first change alone.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
Copilot AI review requested due to automatic review settings August 2, 2026 11:04
@github-actions github-actions Bot added the tests label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

github-actions[bot]
github-actions Bot previously approved these changes Aug 2, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

✅ Mutation Testing Results

Mutation Score: 66.67% (threshold: 60%)

✨ Good job! Mutation score meets the threshold.

What is mutation testing?

Mutation testing measures test quality by introducing small changes (mutations) to the code and checking if tests detect them. A higher score means better test effectiveness.

  • Killed mutants: Tests caught the mutation (good!)
  • Survived mutants: Tests missed the mutation (needs improvement)

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.27%. Comparing base (6b5f157) to head (3fa4d59).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
ofelia.go 75.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #776      +/-   ##
==========================================
- Coverage   89.31%   89.27%   -0.04%     
==========================================
  Files          88       88              
  Lines       12053    12075      +22     
==========================================
+ Hits        10765    10780      +15     
- Misses        994      998       +4     
- Partials      294      297       +3     
Flag Coverage Δ
integration 89.27% <75.00%> (-0.03%) ⬇️
unittests 88.70% <75.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes CLI ergonomics in Ofelia’s top-level command parser so --config and --log-level behave like true global flags (usable before or after the subcommand) and ensures the chosen --config path actually reaches the command implementation rather than being overwritten by per-subcommand defaults.

Changes:

  • Introduce a shared globalOptions struct and register it on the top-level go-flags parser so ofelia --config=… <cmd> and ofelia <cmd> --config=… both parse.
  • Remove per-subcommand --config default tags to prevent go-flags defaults from overwriting the pre-parsed config path.
  • Add e2e coverage to assert both “parses in either position” and “the requested config path is honored.”

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ofelia.go Adds global option declarations and registers them on the top-level parser.
e2e/cli_exit_codes_test.go Adds e2e tests for global flag positioning and honoring the config path.
cli/validate.go Removes --config default so pre-parsed/global config selection isn’t overwritten.
cli/daemon.go Removes --config default so global config selection isn’t overwritten.
cli/config_show.go Removes --config default so global config selection isn’t overwritten.

Comment thread ofelia.go
Review finding from Copilot on PR #776.

Declaring the flag globally fixed the rejection for daemon, validate and
config-show, but doctor was still constructed without the pre-parsed
value, so `ofelia --config=x doctor` was accepted and then disregarded:
it reported on /etc/ofelia/config.ini and told the user the file was
missing. Accepting a flag and ignoring it is worse than rejecting it —
nothing says the path was dropped.

doctor is also the reason the default cannot simply be handed to every
command: given no path it searches well-known locations, and a
pre-filled default would take that away silently. The default is
therefore resolved in run() for the commands that need a concrete file,
while doctor keeps the raw value, so an absent flag still means 'go and
find it'.

Verified against the binary in all four combinations: doctor with the
flag before and after the subcommand uses the given file, doctor without
it still auto-detects (./ofelia.ini), and validate is unchanged.

Signed-off-by: Sebastian Mendel <github@sebastianmendel.de>
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@CybotTM
CybotTM added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 8b0b50e Aug 2, 2026
36 of 37 checks passed
@CybotTM
CybotTM deleted the fix/global-flags-before-subcommand branch August 2, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Global flags are rejected before the subcommand: ofelia --config=x validate fails

2 participants