Skip to content

refactor(config): require an explicit path for config mutations#2862

Merged
max-sixty merged 3 commits into
mainfrom
nix-flake-test-isolation
May 21, 2026
Merged

refactor(config): require an explicit path for config mutations#2862
max-sixty merged 3 commits into
mainfrom
nix-flake-test-isolation

Conversation

@max-sixty
Copy link
Copy Markdown
Owner

The Approvals and UserConfig mutation methods took an Option<&Path>: None meant "resolve the global config path", Some(p) meant "use p". None was a footgun. A unit test that passed None silently wrote to the developer's real ~/.config/worktrunk/: it passed wherever $HOME was writable and failed only in a sandbox with no writable config dir. That is the class of bug behind the recent nix-flake failure (#2853).

This drops the Option. All ten mutation methods (Approvals::{approve_command, approve_commands, revoke_project, clear_all, with_locked_mutation} and UserConfig::{set_skip_shell_integration_prompt, set_skip_commit_generation_prompt, set_project_worktree_path, set_commit_generation_command, with_locked_mutation}) now take &Path. Production callers resolve the path explicitly through two new helpers: require_approvals_path() and require_config_path(), the Result-returning counterparts of approvals_path() / config_path(), matching the require_* accessor convention. A test can no longer reach the real config dir without typing the path; the resolver is no longer a silent default.

HookPlan::approve also skips Approvals::load() on the --yes path. That path approves every project command unconditionally and persists nothing, so the load was dead work. Skipping it removes a useless I/O and keeps wt merge --yes working when approvals.toml is malformed.

tests/CLAUDE.md gains a "Config Isolation for In-Process Unit Tests" section; the approvals_path() doc is updated to describe the lib-crate-only #[cfg(test)] guard.

Testing

Full pre-merge gate green (3811 tests, fmt, clippy, doctests). The three hook_plan tests pass under a non-writable-HOME sandbox repro of the build-sandbox condition.

One coverage note: require_approvals_path() / require_config_path() each have an error branch (ok_or_else(|| ConfigError("Cannot determine …"))) that fires only when no config directory is resolvable. CI always sets $HOME / WORKTRUNK_*_PATH, so that branch is unreachable in tests, and codecov/patch may flag those two lines. The same closure existed before this change, inside with_locked_mutation; the refactor moves it into the new helpers, where it counts as patched lines.

The `Approvals` and `UserConfig` mutation methods took `Option<&Path>`,
where `None` resolved the global config path. A test that passed `None`
silently wrote to the real `~/.config/worktrunk/`: it passed locally and
failed only in a sandbox with no writable config dir.

Drop the `Option`: all ten mutation methods now take `&Path`. Production
callers resolve explicitly through two new helpers,
`require_approvals_path()` and `require_config_path()` (the
`Result`-returning counterparts of `approvals_path()` / `config_path()`).
A test can no longer reach the real config dir without typing the path.

Also: `HookPlan::approve` skips `Approvals::load()` on the `--yes` path.
That path approves unconditionally and persists nothing, so the load was
dead work; skipping it also keeps `wt merge --yes` working when
`approvals.toml` is malformed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

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

CI is red on lint, test (linux), and code-coverage — all three trace to one missed call site:

error[E0308]: mismatched types
  --> src/config/approvals.rs:699:17
   |
699 |                 Some(&path),
   |                 ^^^^^^^^^^^ expected `&Path`, found `Option<&PathBuf>`

That call is inside test_literal_command_text_is_not_normalized_for_approval_matching, which was added in #2852 and merged into main between the time this branch was written and when CI ran. The diff hunks here update the surrounding tests at line 681 and line 711, but the new test fell in the gap and kept its Some(&path). Local pre-merge was green before the rebase pulled in the new call site, which matches the PR description.

The fix is the one-line change you'd expect (Some(&path)&path). Happy to push it if you'd like, otherwise leaving it to you.

max-sixty and others added 2 commits May 21, 2026 12:27
#2851 landed `test_literal_command_text_is_not_normalized_for_approval_matching`
on main using `approve_command`'s old `Option<&Path>` argument. This branch
changed that parameter to `&Path`, so the merge needs the new call site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
worktrunk-bot
worktrunk-bot previously approved these changes May 21, 2026
Copy link
Copy Markdown
Collaborator

@worktrunk-bot worktrunk-bot left a comment

Choose a reason for hiding this comment

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

codecov/patch is failing — 2 missed lines, both inside .ok_or_else(...) closures for the new require_*_path helpers:

  • src/config/user/path.rs:58ConfigError("Cannot determine config directory. Set $HOME or $XDG_CONFIG_HOME".to_string())
  • src/config/approvals.rs:98ConfigError("Cannot determine approvals path. Set $HOME or $XDG_CONFIG_HOME".to_string())

The function bodies (signature, ok_or_else() call, closing brace) are covered via the happy path; only the closure bodies are uncovered. They fire only when config_path() / approvals_path() returns None, which on any supported platform requires both $HOME and $XDG_CONFIG_HOME to be unset and etcetera::choose_base_strategy() to fail — effectively unreachable from test code without process-wide env mutation.

CLAUDE.md says to investigate (write tests, remove unused code, or ask if it's a false positive). I think this is the "false positive" shape: the strings are user-facing error messages the existing pattern (src/shell/paths.rs:16, src/commands/config/state.rs:106) keeps verbatim, and removing them would lose a helpful diagnostic for a real-but-rare failure mode. No general handler to fall through to.

Dismissing my approval per CLAUDE.md. Two reasonable paths from here:

  1. Accept the gap — confirm this is a false positive, merge with codecov/patch failing.
  2. Cover via env-mutation tests — set HOME="" + XDG_CONFIG_HOME="" + ensure WORKTRUNK_CONFIG_PATH/WORKTRUNK_APPROVALS_PATH are unset in a serial test. Possible but fragile; the rest of the suite already avoids this pattern.

Happy to push a (1) re-approval or (2) a coverage commit — whichever you prefer.

@worktrunk-bot worktrunk-bot dismissed their stale review May 21, 2026 20:01

codecov/patch failed — see follow-up review for analysis. Re-approve once the coverage gap is resolved or confirmed as a false positive.

@max-sixty max-sixty merged commit c06b71b into main May 21, 2026
37 of 38 checks passed
@max-sixty max-sixty deleted the nix-flake-test-isolation branch May 21, 2026 20:21
max-sixty added a commit that referenced this pull request May 21, 2026
…ath (#2872)

`require_config_path()` (added in #2862) and the older
`require_user_config_path()` in `src/commands/config/state.rs` both just
wrapped `config_path()` with a `Result`. Two functions, two slightly
different error messages, one purpose.

This deletes `require_user_config_path()` and points its four callers —
`config create` (×2) and `config show` (×2) — at the lib-crate
`require_config_path()`.

The two wrapper tests in `config/mod.rs` are removed with it.
`test_require_user_config_path_returns_ok` tested a trivial wrapper.
`test_require_user_config_path_matches_config_path` guarded issue #1134
(config-create resolving a *different* path than config-loading) — that
regression is now structurally impossible: `config create` / `show`
resolve through `require_config_path()`, which is `config_path()`
itself, so there is no separate resolver left to diverge.

One behavior delta: when no config directory can be resolved, `config
create` / `config show` now error `Cannot determine config directory.
Set $HOME or $XDG_CONFIG_HOME` instead of `Cannot determine config
directory`. No snapshot test references either string.

Follow-up to #2862.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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