Skip to content

feat(git-worktree): auto-trust mise and direnv configs in new worktrees#312

Open
NimbleEngineer21 wants to merge 8 commits intoEveryInc:mainfrom
NimbleEngineer21:feat/git-worktree-trust-dev-tools
Open

feat(git-worktree): auto-trust mise and direnv configs in new worktrees#312
NimbleEngineer21 wants to merge 8 commits intoEveryInc:mainfrom
NimbleEngineer21:feat/git-worktree-trust-dev-tools

Conversation

@NimbleEngineer21
Copy link

@NimbleEngineer21 NimbleEngineer21 commented Mar 18, 2026

Summary

  • Add trust_dev_tools() function to worktree-manager.sh that automatically runs mise trust and direnv allow after creating a new worktree
  • Safety gate: only auto-trusts configs that are unchanged from the repo's default branch (detected via origin/HEAD, falling back to main); modified configs (e.g., from a PR) are flagged for manual review
  • Update help text and SKILL.md to document the new behavior

Problem

When worktree-manager.sh creates a new worktree, tools like mise and direnv don't recognize the new filesystem path as trusted. When hooks (e.g. lefthook pre-commit) invoke mise x -- <command>, mise opens an interactive trust prompt that hangs indefinitely since hook subprocesses can't forward user input. direnv has the same trust model with direnv allow.

Approach

Follows the existing copy_env_files pattern: a dedicated function called from create_worktree() immediately after env file copying. Double-guarded by command -v (tool installed?) and file existence checks (config present?) -- completely invisible to developers who don't use these tools.

Safety model: Before auto-trusting, each config file is hash-compared against the repo's default branch via _config_unchanged(). The comparison always uses the default branch (not from_branch), so passing a PR branch as the base doesn't bypass the safety check. This means:

  • Feature work from main: configs are identical to main -> auto-trusted -> no hanging hooks
  • PR review where configs are unchanged: same content as main -> auto-trusted -> still convenient
  • PR review where .envrc/.mise.toml was modified or added: configs differ -> skipped with a yellow warning listing what was skipped and how to trust manually

mise: trusts .mise.toml, mise.toml, or .tool-versions if unchanged from default branch
direnv: allows .envrc if unchanged from default branch

Error handling: Trust failures surface warnings (no 2>/dev/null). When all attempts fail, an explicit warning with manual instructions is shown. Attempted vs successful trusts are tracked separately so "nothing to do" is distinguishable from "everything failed."

Test evidence

All tests run against a temporary git repo. direnv was tested via a mock script (same _config_unchanged gate as mise). Trust failures simulated with mock scripts that exit non-zero.

✅ Test 1: Auto-trusts unchanged config

Scenario: .mise.toml exists on main, worktree created from main (config unchanged).

Creating worktree: test-unchanged-config
  From: main
Updating main...
Already on 'main'
Already up to date.
Creating worktree...
Preparing worktree (new branch 'test-unchanged-config')
HEAD is now at ae35a78 add mise config
Copying environment files...
  ℹ️  No .env files found in main repository
  ✓ Trusted 1 dev tool config(s)
✓ Worktree created successfully!

Verified with mise trust --show:

/tmp/.../test-repo: untrusted
/tmp/.../test-repo/.worktrees/test-unchanged-config: trusted
✅ Test 2: Skips modified config with warning

Scenario: Branch modifies .mise.toml (changes node version, adds python). Worktree created from that branch.

Creating worktree: test-modified-config
  From: pr-modifies-mise
Updating pr-modifies-mise...
Creating worktree...
Preparing worktree (new branch 'test-modified-config')
HEAD is now at 0588ab0 modify mise config
Copying environment files...
  ℹ️  No .env files found in main repository
  Skipped auto-trust for modified config(s):
    - .mise.toml (mise)
  Review the diff, then trust manually: cd .../.worktrees/test-modified-config && mise trust && direnv allow
✓ Worktree created successfully!
✅ Test 3: Silent when no configs present

Scenario: No .mise.toml, .tool-versions, or .envrc in the repo.

Creating worktree: test-no-configs
  From: main
Updating main...
Creating worktree...
Preparing worktree (new branch 'test-no-configs')
Copying environment files...
  ℹ️  No .env files found in main repository
✓ Worktree created successfully!

No trust output at all -- completely invisible.

✅ Test 4: Help output shows new section
Dev Tool Trust:
  - Trusts mise config (.mise.toml, mise.toml, .tool-versions) and direnv (.envrc)
  - Only auto-trusts configs unchanged from the base branch
  - Modified configs are flagged for manual review (safety for PR reviews)
  - Only runs if the tool is installed and config exists
  - Prevents hooks/scripts from hanging on interactive trust prompts
✅ Test 5: Skips newly added config

Scenario: Branch adds .mise.toml that doesn't exist on main.

Creating worktree: test-new-mise
  From: pr-adds-mise
Updating pr-adds-mise...
Creating worktree...
Preparing worktree (new branch 'test-new-mise')
HEAD is now at 5d3ff33 add mise config
Copying environment files...
  ℹ️  No .env files found in main repository
  Skipped auto-trust for modified config(s):
    - .mise.toml (mise)
  Review the diff, then trust manually: cd .../.worktrees/test-new-mise && mise trust && direnv allow
✓ Worktree created successfully!
✅ Test 6: direnv allow runs for unchanged .envrc

Scenario: Both .mise.toml and .envrc exist on main, unchanged. direnv tested via mock script.

Creating worktree: test-direnv-unchanged
  From: main
Updating main...
Creating worktree...
Preparing worktree (new branch 'test-direnv-unchanged')
HEAD is now at a358134 add mise and envrc configs
Copying environment files...
  ⚠️  .envrc already exists, backing up to .envrc.backup
  ✓ Copied .envrc
  ✓ Copied 1 environment file(s)
direnv: loading .envrc
  ✓ Trusted 2 dev tool config(s)
✓ Worktree created successfully!
✅ Test 7: PR branch as from_branch -- still compares against default branch

Scenario: PR branch modifies both .mise.toml and .envrc. Passed as from_branch. Safety gate should still compare against main.

Creating worktree: test-from-pr-branch
  From: pr-modifies-both
Updating pr-modifies-both...
Creating worktree...
Preparing worktree (new branch 'test-from-pr-branch')
HEAD is now at 283f31f modify both configs
Copying environment files...
  ⚠️  .envrc already exists, backing up to .envrc.backup
  ✓ Copied .envrc
  ✓ Copied 1 environment file(s)
  Skipped auto-trust for modified config(s):
    - .mise.toml (mise)
    - .envrc (direnv)
  Review the diff, then trust manually: cd .../.worktrees/test-from-pr-branch && mise trust && direnv allow
✓ Worktree created successfully!

Both configs correctly skipped despite from_branch being the PR branch.

✅ Test 8: Trust failure shows warnings

Scenario: Both mise trust and direnv allow fail (simulated via mock scripts that exit non-zero). Configs are unchanged from main.

Creating worktree: test-trust-failure
  From: main
Updating main...
Creating worktree...
Preparing worktree (new branch 'test-trust-failure')
HEAD is now at a358134 add mise and envrc configs
Copying environment files...
  ⚠️  .envrc already exists, backing up to .envrc.backup
  ✓ Copied .envrc
  ✓ Copied 1 environment file(s)
mise error: permission denied writing trust database
  Warning: 'mise trust' failed -- run manually in .../.worktrees/test-trust-failure
direnv: error: permission denied
  Warning: 'direnv allow' failed -- run manually in .../.worktrees/test-trust-failure
  Warning: dev tool trust was attempted but all 2 config(s) failed
  Run 'mise trust' / 'direnv allow' manually in .../.worktrees/test-trust-failure
✓ Worktree created successfully!

Errors surfaced clearly. Worktree creation still succeeds (trust is non-blocking).

Test plan

  • Auto-trust unchanged .mise.toml (Test 1)
  • Skip modified .mise.toml with warning (Test 2)
  • Silent when no configs present (Test 3)
  • Help output shows Dev Tool Trust section (Test 4)
  • Skip newly added .mise.toml with warning (Test 5)
  • direnv allow runs for unchanged .envrc (Test 6)
  • PR branch as from_branch still compares against default branch (Test 7)
  • Trust failure shows warnings, worktree creation continues (Test 8)

When worktree-manager.sh creates a new worktree, tools like mise and
direnv don't recognize the new filesystem path as trusted. This causes
hooks (e.g. lefthook pre-commit) that invoke `mise x -- <command>` to
hang on an interactive trust prompt that can never be answered.

Add a trust_dev_tools() function that runs `mise trust` and
`direnv allow` in the new worktree immediately after creation. The fix
is completely invisible to developers who don't use these tools
(double-guarded by command -v and file existence checks). Follows the
existing copy_env_files pattern.
@tmchow
Copy link
Collaborator

tmchow commented Mar 18, 2026

@codex review please

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c25d98e791

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Security:
- Only auto-trust configs unchanged from the base branch
- Modified configs (e.g., from a PR) are flagged with a warning and
  manual trust instructions, preventing code execution from untrusted branches

Error handling:
- Remove 2>/dev/null; show warnings on trust failure
- Track attempted vs successful trusts; warn when all attempts fail

Comments & docs:
- Fix mise comment: trust is directory-scoped, not per-file
- Drop inaccurate "silently fail" from function doc
- Update SKILL.md feature list and critical section
- Fix help text indentation to match existing style
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdb688f81f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@NimbleEngineer21
Copy link
Author

NimbleEngineer21 commented Mar 18, 2026

@tmchow Addressed the Codex review feedback in bdb688f. The main change: trust_dev_tools now only auto-trusts configs that are unchanged from the base branch (hash-compared via _config_unchanged()). Modified or newly added configs are skipped with a warning and manual trust instructions. Also improved error handling — trust failures now surface warnings instead of being silently swallowed. Updated the PR description with the full safety model and expanded test plan w/ results

…from_branch

When from_branch is a PR branch, comparing configs against it would
trivially pass since the worktree was just created from that ref. Always
compare against the repo's default branch (detected via origin/HEAD,
falling back to main) so the safety gate works regardless of which
branch the worktree is created from.
Copy link
Collaborator

@tmchow tmchow left a comment

Choose a reason for hiding this comment

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

Good contribution -- the problem is real and the safety model (unchanged-from-default-branch = safe to auto-trust) is the right call. Two issues in the safety gate need to be fixed before this can merge. Both are small fixes in the same spot.

1. origin/HEAD fallback silently produces an empty string -- when origin/HEAD isn't configured (common with --single-branch clones), sed exits 0 on empty input so the || fallback never fires. default_branch becomes "", and _config_unchanged ends up doing git show ":$file" (index lookup), which can silently bypass the safety gate.

2. Comparison uses local branch ref instead of remote tracking ref -- git show "main:$file" reads from the local main, which create_worktree doesn't pull when from_branch is a PR branch. Stale local main means an inaccurate comparison.

See inline comment for the fix -- three lines.

…ch detection

Address tmchow's review:
1. Without set -o pipefail, failed git symbolic-ref piped through sed
   still exits 0, making the || fallback never fire and default_branch
   silently become "". Use ${default_branch:-main} for safe fallback.
2. Use origin/$default_branch instead of bare branch name so
   _config_unchanged always compares against the remote tracking ref,
   not a potentially stale local ref.
@NimbleEngineer21 NimbleEngineer21 requested a review from tmchow March 19, 2026 02:07
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fab2fb225

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

P1 fixes:
- Fetch origin/$default_branch before comparing so ref is fresh even
  when from_branch is a PR branch
- Pass specific config file to mise trust ($f) instead of bare
  mise trust, which may not trust .tool-versions repos
- Rename from_branch param to base_ref in trust_dev_tools to prevent
  future contributors from accidentally passing the wrong value

P2 fixes:
- Drop attempted counter; per-tool warnings already distinguish
  "nothing to do" from "everything failed"
- Build manual trust command from skipped array instead of hardcoding
  both mise trust and direnv allow
- Rename wt_path to worktree_path in _config_unchanged for consistency
- Collapse double git show into single call with || return 1

P3 fixes:
- Document TOCTOU race condition as accepted risk (requires local
  filesystem write access + timing on single-user dev machines)
- Document gitattributes line-ending edge case (mismatch causes
  false negative which is the safe direction)
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bad2a46f3c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

…tput

1. Separate git show from git hash-object pipe in _config_unchanged so
   || return 1 actually fires when the file doesn't exist on the base
   branch. Without pipefail, the previous pipeline always exited 0
   because git hash-object --stdin succeeds on empty input.

2. Join skipped[] entries with ' && ' instead of space-concatenating,
   so the suggested manual command is valid shell when both mise and
   direnv configs are skipped.

3. Warn on git fetch failure instead of silent || true, so the user
   knows the trust comparison may use stale data.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9428d31b0a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

The previous approach captured git show output via command substitution,
which strips trailing newlines. This caused the base hash to always
differ from the worktree file hash, making auto-trust effectively dead
code (every file appeared "modified").

Use git rev-parse to get the blob SHA directly from git's object
database. This avoids content round-tripping through shell variables,
handles binary files correctly, and is simpler than both the original
pipe approach and the separated git show approach.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 369be4cccd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

When git fetch fails AND the ref doesn't exist locally at all, there is
no baseline to compare against. Proceed with stale-but-present refs
(warn only), but skip trust entirely when the ref is absent. This fails
closed when there is truly no data without penalizing developers who
are temporarily offline with a recently-fetched ref.
Copy link
Collaborator

@tmchow tmchow left a comment

Choose a reason for hiding this comment

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

@NimbleEngineer21 a few more things to just squeeze in while we are pounding on this.

Stale comment references git show

The _config_unchanged comment says "git show pipes raw bytes" but the code now uses git rev-parse. It will mislead future maintainers. Suggested replacement:

  # Note: rev-parse returns the stored blob hash; hash-object on a path applies
  # gitattributes filters. A mismatch causes a false negative (trust skipped),
  # which is the safe direction.

git fetch stderr silenced with 2>/dev/null

Drop 2>/dev/null from the fetch — --quiet already suppresses normal output. Let actual errors (auth failures, DNS issues) flow through so users can debug trust failures.

Add symlink guard to _config_unchanged

While the current behavior is fail-closed for symlinks, an explicit [[ -L "$worktree_path/$file" ]] && return 1 check makes the intent clear and guards against regressions.

Add non-empty hash guard

Change the comparison to [[ -n "$base_hash" && -n "$worktree_hash" && "$base_hash" == "$worktree_hash" ]]. Costs nothing, eliminates the theoretical empty-string-equals-empty-string case.

Compress TOCTOU comment

Super minor but the 3-line TOCTOU comment raises a concern then immediately dismisses it. Either remove or compress to one line:

  # TOCTOU between hash-check and trust is acceptable for local dev use.

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