Skip to content

fix(set): write to lowest-priority existing config file#331

Merged
jdx merged 6 commits intomainfrom
fix/set-write-target
Mar 8, 2026
Merged

fix(set): write to lowest-priority existing config file#331
jdx merged 6 commits intomainfrom
fix/set-write-target

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 8, 2026

Summary

  • fnox set now writes to the lowest-priority existing config file in the current directory instead of always targeting fnox.toml
  • If only fnox.local.toml exists, secrets are written there instead of creating a new fnox.toml
  • If both fnox.toml and fnox.local.toml exist, writes to fnox.toml (lowest priority)
  • Falls back to fnox.toml if no config files exist yet
  • Adds find_local_config() helper in config.rs for reuse by other commands

Test plan

  • 6 unit tests for find_local_config covering: no files, only fnox.toml, only fnox.local.toml, both exist, dotfile, profile-specific
  • 2 new bats integration tests for fnox set write target behavior
  • All existing config_hierarchy_set bats tests still pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes where secrets are persisted on disk, which can surprise users and affect repo state (creating/modifying different config files), but the logic is localized and covered by new unit/integration tests.

Overview
Updates fnox set to write into the most appropriate existing config file in the current directory: it now prefers an existing profile-specific file (e.g. fnox.staging.toml) when a non-default profile is active, otherwise chooses the lowest-priority existing base file (e.g. fnox.toml over fnox.local.toml), falling back to creating fnox.toml when nothing exists.

Introduces config::DEFAULT_CONFIG_FILENAME and a reusable config::find_local_config() helper, and adds unit + Bats integration tests to lock in the new write-target behavior.

Written by Cursor Bugbot for commit bc2d3ec. This will update automatically on new commits. Configure here.

Previously `fnox set` always wrote to `fnox.toml` regardless of which
config files existed. Now it finds the lowest-priority existing config
file in the current directory (e.g. writes to `fnox.local.toml` if
that's the only config present, or to `fnox.toml` if both exist).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the fnox set command's behavior by introducing a more sophisticated mechanism for determining where configuration values should be written. Instead of a fixed target, the command now dynamically identifies the most appropriate existing local configuration file based on a defined priority, improving flexibility and user experience, especially in projects utilizing fnox.local.toml for environment-specific settings.

Highlights

  • Config File Selection: The fnox set command now intelligently selects the lowest-priority existing configuration file in the current directory for writing, rather than defaulting to fnox.toml.
  • fnox.local.toml Preference: If only fnox.local.toml exists, secrets are now written to it, preventing the creation of an unnecessary fnox.toml.
  • fnox.toml Priority: When both fnox.toml and fnox.local.toml are present, fnox set correctly targets fnox.toml as the lowest priority.
  • Fallback Behavior: If no configuration files exist, fnox set will fall back to creating and writing to fnox.toml.
  • New Helper Function: A find_local_config() helper function was introduced in config.rs to encapsulate this logic for reuse.
Changelog
  • src/commands/set.rs
    • Updated the set command to utilize the new config::find_local_config function for determining the target configuration file for writes.
  • src/config.rs
    • Added a new public function find_local_config which identifies the lowest-priority existing configuration file within a given directory.
    • Included comprehensive unit tests for find_local_config covering various scenarios like no files, only fnox.toml, only fnox.local.toml, both existing, dotfiles, and profile-specific files.
  • test/config_hierarchy_set.bats
    • Introduced two new integration tests to validate the fnox set command's behavior when writing to fnox.local.toml (when it's the only file) and fnox.toml (when both fnox.toml and fnox.local.toml exist).
Activity
  • The author implemented 6 unit tests for the new find_local_config function, covering various scenarios including the absence of files, the presence of only fnox.toml, only fnox.local.toml, both files, dotfiles, and profile-specific configurations.
  • Two new Bats integration tests were added to verify the fnox set command's behavior regarding its write target.
  • All existing config_hierarchy_set Bats tests were confirmed to still pass after these changes.
  • The code was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request modifies the fnox set command to write to the lowest-priority existing configuration file. While the implementation introduces a clean find_local_config helper in config.rs and is well-tested, a path traversal vulnerability has been identified. The profile name from the CLI flag is not validated, which could allow an attacker to manipulate file paths used for loading and saving configurations. It is recommended to apply the same validation logic used for the FNOX_PROFILE environment variable to the CLI flag to address this security concern.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 8, 2026

Greptile Summary

This PR fixes fnox set so it writes to the lowest-priority existing config file in the current directory instead of always targeting fnox.toml. It introduces config::DEFAULT_CONFIG_FILENAME (eliminating the hardcoded-string duplication risk) and a new config::find_local_config() helper that encapsulates the selection logic with 11 unit tests and 3 new bats integration tests.

Key behavioral changes:

  • When only fnox.local.toml exists, secrets are now written there instead of creating a new fnox.toml
  • When both fnox.toml and fnox.local.toml exist, fnox.toml (lowest priority) is chosen
  • When --profile staging is active and fnox.staging.toml exists, the profile-specific file is preferred
  • When a non-default profile is active and no profile-specific file exists, fnox.local.toml is deliberately skipped to prevent profile-scoped secrets from landing silently in a gitignored file

All issues raised in prior review rounds have been addressed: the hardcoded sentinel string is replaced with a shared constant, redundant re-checks of profile-specific files in the fallback are eliminated, and profile secrets are no longer silently routed to fnox.local.toml.

Confidence Score: 4/5

  • PR is safe to merge; the write-target selection logic is well-reasoned and covered by unit + integration tests.
  • All six issues from previous review rounds are addressed. The new find_local_config logic is correct for all tested scenarios, and the unit tests cover the edge cases (no files, base only, local only, both, dotfile, profile with/without base, profile skips local). The one known remaining limitation—--config fnox.toml explicitly typed by the user is still indistinguishable from the CLI default—is a pre-existing structural constraint of clap's PathBuf default handling and does not introduce a regression compared to the prior behavior.
  • No files require special attention; the core logic in src/config.rs has thorough unit test coverage.

Important Files Changed

Filename Overview
src/config.rs Adds DEFAULT_CONFIG_FILENAME constant and find_local_config() helper with 11 unit tests. Logic correctly handles profile-specific preference, base-file fallback, and profile-vs-local exclusion. Minor style nit: is_profiled is computed after the early-return block when it could be hoisted to the top of the function for clarity.
src/commands/set.rs Wires find_local_config into the write-path when --config is at its CLI default. Uses the shared DEFAULT_CONFIG_FILENAME constant instead of a hardcoded string. Remaining known limitation: explicitly passing --config fnox.toml is still indistinguishable from the default and triggers auto-detection.
src/commands/mod.rs One-line change: replaces the hardcoded "fnox.toml" default value with crate::config::DEFAULT_CONFIG_FILENAME, eliminating the duplication risk.
test/config_hierarchy_set.bats Adds three integration tests: local-only write target, both-files write target (prefers lowest priority), and profile-specific write target. Good coverage of the new selection logic at the bats layer.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["fnox set KEY val"] --> B{is --global?}
    B -- yes --> C[Write to global config path]
    B -- no --> D{cli.config == DEFAULT_CONFIG_FILENAME?}
    D -- no --> E["Write to current_dir.join(cli.config)"]
    D -- yes --> F["find_local_config(current_dir, profile)"]
    F --> G{Non-default profile?}
    G -- yes --> H{fnox.PROFILE.toml or .fnox.PROFILE.toml exists?}
    H -- yes --> I[Return profile-specific file]
    H -- no --> J
    G -- no --> J{fnox.toml or .fnox.toml exists?}
    J -- yes --> K[Return base file - lowest priority]
    J -- no --> L{is_profiled?}
    L -- yes --> M[Return fnox.toml - new file]
    L -- no --> N{fnox.local.toml or .fnox.local.toml exists?}
    N -- yes --> O[Return local file]
    N -- no --> M
Loading

Last reviewed commit: bc2d3ec

Comment thread src/config.rs Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread src/commands/set.rs Outdated
- When --config is explicitly set to a non-default path, use it
  directly instead of find_local_config (fixes set_no_provider tests)
- Fix misleading doc comment on all_config_filenames: dotfiles come
  second in the list (higher priority), not first
- Add test for profile + base file coexistence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/commands/set.rs Outdated
jdx and others added 2 commits March 8, 2026 17:12
Only use auto-detection when --config equals the clap default
"fnox.toml". Any other value (including other standard filenames like
fnox.local.toml) is treated as an explicit user choice and respected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When --profile staging is used and fnox.staging.toml exists, write to
it instead of fnox.toml. This keeps secrets scoped to the correct
profile rather than leaking them into the base config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/commands/set.rs Outdated
Comment thread src/config.rs Outdated
Comment thread test/config_hierarchy_set.bats
…test

- Extract shared DEFAULT_CONFIG_FILENAME constant used by Cli default,
  set.rs detection, and find_local_config fallback
- Avoid redundant stat calls for profile files in find_local_config
  fallback by passing None to all_config_filenames
- Add bats integration test for --profile staging writing to
  fnox.staging.toml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread src/config.rs Outdated
When a non-default profile is active and no profile-specific config
file exists, the fallback now excludes fnox.local.toml to avoid
silently routing profile-scoped secrets into a gitignored file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx enabled auto-merge (squash) March 8, 2026 21:50
@jdx jdx merged commit a96265d into main Mar 8, 2026
16 checks passed
@jdx jdx deleted the fix/set-write-target branch March 8, 2026 21:58
jdx pushed a commit that referenced this pull request Mar 9, 2026
### 🚀 Features

- **(cloudflare)** add Cloudflare API token lease backend by
[@jdx](https://github.com/jdx) in
[#335](#335)
- **(fido2)** bump demand to v2, mask PIN during typing by
[@jdx](https://github.com/jdx) in
[#334](#334)
- **(init)** add -f as short alias for --force by
[@jdx](https://github.com/jdx) in
[#329](#329)
- **(lease)** add --all flag, default to creating all leases by
[@jdx](https://github.com/jdx) in
[#337](#337)
- **(lease)** add GitHub App installation token lease backend by
[@jdx](https://github.com/jdx) in
[#342](#342)

### 🐛 Bug Fixes

- **(config)** fix directory locations to follow XDG spec by
[@jdx](https://github.com/jdx) in
[#336](#336)
- **(exec)** use unix exec and exit silently on subprocess failure by
[@jdx](https://github.com/jdx) in
[#339](#339)
- **(fido2)** remove duplicate touch prompt by
[@jdx](https://github.com/jdx) in
[#332](#332)
- **(set)** write to lowest-priority existing config file by
[@jdx](https://github.com/jdx) in
[#331](#331)
- **(tui)** skip providers requiring interactive auth by
[@jdx](https://github.com/jdx) in
[#333](#333)

### 🛡️ Security

- **(ci)** retry lint step to handle transient pkl fetch failures by
[@jdx](https://github.com/jdx) in
[#341](#341)
- **(mcp)** add MCP server for secret-gated AI agent access by
[@jdx](https://github.com/jdx) in
[#343](#343)
- add guide for fnox sync by [@jdx](https://github.com/jdx) in
[#328](#328)

### 🔍 Other Changes

- share Rust cache across CI jobs by [@jdx](https://github.com/jdx) in
[#340](#340)
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