Skip to content

fix(hook): close branch-guard bypasses for bare filenames, new dirs, missing path fields - #291

Merged
getappz merged 5 commits into
masterfrom
fix/hook-redirect-branch-guard-bypass
Jul 21, 2026
Merged

fix(hook): close branch-guard bypasses for bare filenames, new dirs, missing path fields#291
getappz merged 5 commits into
masterfrom
fix/hook-redirect-branch-guard-bypass

Conversation

@getappz

@getappz getappz commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Follow-up to fix(hook): resolve PreToolUse branch guard against target file's repo, not host cwd #283: CodeRabbit flagged a critical finding on that PR (inline review comment) that was squash-merged without a fix.
  • redirect_decision's target-repo resolution called repo_toplevel() directly on file_path.parent(), which fails (ENOENT) and silently skips the branch guard for a bare filename (parent ""), a new file under a not-yet-created directory, or any mutating tool call with no top-level file_path/path (e.g. MultiEdit) — no cwd fallback existed for that last case despite fix(hook): resolve PreToolUse branch guard against target file's repo, not host cwd #283's stated intent.
  • Fix: walk Path::ancestors() to the first directory that actually exists before resolving its repo toplevel, and fall back to cwd when the path can't be extracted at all. Genuinely out-of-repo targets still skip the guard as before.

Test plan

Summary by CodeRabbit

  • Bug Fixes
    • Improved branch protection for file changes across nested and newly created directories.
    • Restored protection for bare filenames and tool inputs without an explicit file path.
    • Targets outside a Git repository remain unguarded as expected.
  • Tests
    • Added coverage for repository detection, working-directory fallbacks, and paths that do not yet exist.

…and missing path fields

CodeRabbit flagged this as critical on PR #283 but it was squash-merged
without a fix. target_repo resolution called repo_toplevel() directly
on p.parent(), which fails (and silently skips the branch guard) for:
- a bare filename (parent is \, ENOENT)
- a new file under a not-yet-created directory (parent doesn't exist)
- a MUTATING_TOOLS call with no top-level file_path/path (e.g. MultiEdit)

Now walks ancestors to the first directory that actually exists on disk
before resolving its repo toplevel, and falls back to cwd when the path
can't be extracted at all -- matching the original bug's intended fix.

Agentflare-Agent: claude-code_2-1-216_agent
Agentflare-Branch: fix/hook-redirect-branch-guard-bypass
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

redirect_decision now resolves mutating-tool repositories by walking target path ancestors, falls back to the host cwd when needed, and adds synchronized regression tests for nested, bare, missing-field, and outside-repository inputs.

Changes

Mutating-tool repository resolution

Layer / File(s) Summary
Path-based repository guard
src/hook_redirect.rs
Mutating-tool paths are resolved through existing ancestors, branch guards use the discovered repository, and targets outside git repositories remain unguarded.
Repository resolution regression coverage
src/hook_redirect.rs
CWD-safe test helpers and temporary repositories support regression tests for bare filenames, missing directories, missing path fields, and external targets.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: closing branch-guard bypasses for filename, new-directory, and missing-path cases.
Description check ✅ Passed The description covers Summary and Test plan well, but it omits the template's Notes for reviewers section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hook-redirect-branch-guard-bypass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/hook_redirect.rs (1)

163-175: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant repo_toplevel subprocess calls beyond the first existing ancestor.

git rev-parse --show-toplevel already walks up from its start directory to find .git, so once it's called on the first existing ancestor and returns None, every further (higher) existing ancestor is guaranteed to also return None — they're strict super-ancestors already covered by that first call's internal walk. As written, find_map will keep re-invoking repo_toplevel (a new git subprocess spawn) for every subsequent existing ancestor instead of stopping after the first. For a target path with several existing directory levels outside any repo, this spawns one subprocess per level instead of one, adding latency that competes with GATING_TIMEOUT — ironically risking a fail-open skip of the very guard this PR hardens.

♻️ Only call repo_toplevel once, on the first existing ancestor
-            let target_repo = target_path.and_then(|p| {
-                p.ancestors().skip(1).find_map(|ancestor| {
-                    let check = if ancestor == Path::new("") {
-                        Path::new(".")
-                    } else {
-                        ancestor
-                    };
-                    check
-                        .exists()
-                        .then(|| flare_git_core::branch::repo_toplevel(check))
-                        .flatten()
-                })
-            });
+            let target_repo = target_path.and_then(|p| {
+                let first_existing = p.ancestors().skip(1).find(|ancestor| {
+                    let check = if *ancestor == Path::new("") {
+                        Path::new(".")
+                    } else {
+                        *ancestor
+                    };
+                    check.exists()
+                })?;
+                let check = if first_existing == Path::new("") {
+                    Path::new(".")
+                } else {
+                    first_existing
+                };
+                flare_git_core::branch::repo_toplevel(check)
+            });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hook_redirect.rs` around lines 163 - 175, Update the target_repo
resolution around target_path so it selects only the first existing ancestor
before calling flare_git_core::branch::repo_toplevel. Replace the repeated
find_map subprocess calls with logic that stops at that ancestor and invokes
repo_toplevel exactly once, while preserving the Path::new("") to "."
normalization and the existing Option result behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hook_redirect.rs`:
- Around line 163-175: Update the target_repo resolution around target_path so
it selects only the first existing ancestor before calling
flare_git_core::branch::repo_toplevel. Replace the repeated find_map subprocess
calls with logic that stops at that ancestor and invokes repo_toplevel exactly
once, while preserving the Path::new("") to "." normalization and the existing
Option result behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2eb81b52-84c6-45e0-bbde-aeecc158e4cf

📥 Commits

Reviewing files that changed from the base of the PR and between f7aefcc and 54a80c8.

📒 Files selected for processing (1)
  • src/hook_redirect.rs

@getappz
getappz enabled auto-merge (squash) July 21, 2026 12:44
getappz added 4 commits July 21, 2026 18:15
Two issues found reviewing this PR's own CI run:

- The ancestor-walk tests used std::env::set_current_dir, which is
  global process state -- it collided with an unrelated parallel test
  (hook::tests::session_start_message_shows_pending_items_from_backend_db,
  which resolves its project from cwd) and broke CI's ubuntu build.
  Rewritten to use absolute paths and cwd-independent ground-truth
  comparisons instead, so no test here touches the real cwd.

- CodeRabbit nitpick on the ancestor walk: repo_toplevel() (a git
  subprocess) was being re-invoked for every existing ancestor instead
  of stopping at the first one, since git rev-parse --show-toplevel
  already walks upward internally. Now finds the first existing
  ancestor and calls repo_toplevel exactly once.

Agentflare-Agent: claude-code_2-1-216_agent
Agentflare-Branch: fix/hook-redirect-branch-guard-bypass
…b.com/getappz/agentflare into fix/hook-redirect-branch-guard-bypass

Agentflare-Agent: claude-code_2-1-216_agent
Agentflare-Branch: fix/hook-redirect-branch-guard-bypass
…ranch-guard-bypass

# Conflicts:
#	src/hook_redirect.rs

Agentflare-Agent: claude-code_2-1-216_agent
Agentflare-Branch: fix/hook-redirect-branch-guard-bypass
@getappz
getappz merged commit dd13fae into master Jul 21, 2026
16 checks passed
@getappz
getappz deleted the fix/hook-redirect-branch-guard-bypass branch July 21, 2026 13:31
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