Skip to content

feat(git): worktree orphan audit + fix git_binary shims-dir self-deny - #304

Merged
getappz merged 4 commits into
masterfrom
feat/worktree-audit-294
Jul 22, 2026
Merged

feat(git): worktree orphan audit + fix git_binary shims-dir self-deny#304
getappz merged 4 commits into
masterfrom
feat/worktree-audit-294

Conversation

@getappz

@getappz getappz commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • agentflare git audit preview|prune [--all]: previews/removes orphaned .worktrees/task/* dirs (broken .git gitdir pointer), excluding anything matching a live claimed item. Prune snapshots before removing, then runs git worktree prune. Closes item Feature: Embedded Anthropic→OpenAI proxy with multi-backend failover #294.
  • Fixes git_binary() (crates/flare-git-core/src/shell.rs) resolving back to agentflare's own git PATH shim once ~/.agentflare/shims is prepended to PATH (item feat(dev-install): build and install PATH shims alongside the main binary #301's dev-install shim bundling) — the shim unconditionally denies git worktree, so agentflare's own create_worktree (the item claim flow) was silently self-deadlocking. Also ports mise's case-insensitive paths_eq comparator for the exclusion check.

Test plan

  • cargo fmt --check
  • cargo clippy -p flare-git-core -p agentflare-backend --all-features -- -D warnings
  • cargo test -p flare-git-core -p agentflare-backend — 84 passed
  • CI / CodeRabbit review

Summary by CodeRabbit

  • New Features

    • Added agentflare git audit to preview orphaned worktrees and identify their locations, sizes, and status.
    • Added options to prune selected orphaned worktrees or remove all detected orphans.
    • Active claims are respected to help prevent removal of worktrees still in use.
  • Bug Fixes

    • Improved Git executable discovery to avoid conflicting shim and build directories, reducing recursion and deadlock risks.

getappz added 2 commits July 22, 2026 16:39
ensure_on_path prepends ~/.agentflare/shims to the user's persistent PATH.
git_binary()'s exclusion filter only excluded this process's own dir and
cargo target dirs, so agentflare's own worktree creation (create_worktree,
called from the item claim flow) resolved git back to its own PATH shim --
which denies 'worktree' unconditionally, and the denial looked like an
ordinary git error to the soft-fail-on-error caller, silently deadlocking
the claim flow.

Also ports mise's paths_eq comparator (case-insensitive, separator-
normalized) instead of raw PathBuf equality, since a case or / vs mismatch on Windows/macOS would silently reintroduce the same bug.

Agentflare-Agent: claude-code_2-1-217_agent
Agentflare-Branch: feat/worktree-audit-294
…shot (#294)

agentflare git audit preview: lists .worktrees/task/* dirs with a broken
.git gitdir pointer, excluding any directory whose name matches a live
claimed item's sequence_id.

agentflare git audit prune [names...|--all]: removes the listed orphans,
snapshotting each one first (same pre-destructive-snapshot rationale as
reset --hard/clean -f), then runs 'git worktree prune' to clear git's
own metadata.

Automates the manual cleanup done in the 2026-07-21 branch/worktree sweep
(37 branches -> 13 kept, .worktrees/ 942MB -> 316KB).

Agentflare-Agent: claude-code_2-1-217_agent
Agentflare-Branch: feat/worktree-audit-294
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@getappz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a2132ea-0425-4eed-a9d5-809eab93a9d2

📥 Commits

Reviewing files that changed from the base of the PR and between 65b6d32 and 075c11d.

📒 Files selected for processing (3)
  • crates/flare-git-core/src/shell.rs
  • crates/flare-git-core/src/worktree.rs
  • src/cli/git.rs
📝 Walkthrough

Walkthrough

Adds orphan worktree auditing and pruning through the Git CLI, protects active claimed items, exports claim helpers, and filters Agentflare shim paths during git binary resolution.

Changes

Orphan worktree audit

Layer / File(s) Summary
Active claim lookup
crates/agentflare-backend/src/claim.rs
Exports the default claim TTL helper and adds active claim ID listing.
Orphan worktree detection
crates/flare-git-core/src/worktree.rs, crates/flare-git-core/Cargo.toml
Detects broken task worktrees, records metadata and size, and uses walkdir for recursive size calculation.
Audit CLI and cleanup
src/cli/git.rs, crates/flare-git-core/src/worktree.rs
Adds preview and prune commands, filters claimed sequence IDs, deletes selected or all orphan directories, and prunes git worktree metadata.

Git PATH filtering

Layer / File(s) Summary
Git binary resolution safeguards
crates/flare-git-core/src/shell.rs
Excludes executable and Agentflare shim directories from PATH lookup using platform-aware path comparisons and adds coverage for the behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant GitCLI
  participant ClaimDatabase
  participant WorktreeFilesystem
  participant Git
  Operator->>GitCLI: audit preview or prune
  GitCLI->>ClaimDatabase: load active claimed sequence IDs
  GitCLI->>WorktreeFilesystem: scan orphan task worktrees
  WorktreeFilesystem-->>GitCLI: return orphan records
  GitCLI->>WorktreeFilesystem: delete selected orphan directories
  WorktreeFilesystem->>Git: run git worktree prune
  GitCLI-->>Operator: print audit or pruning results
Loading

Possibly related PRs

Suggested labels: enhancement, rust

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main worktree audit and git_binary shim fix.
Description check ✅ Passed The description covers the required Summary and Test plan sections and is mostly complete despite a missing 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 feat/worktree-audit-294

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.

…or cases

The combined test hand-built a backslash Windows path and asserted it
matches on macOS too, but macOS never treats '\' as a separator --
Path::components() splits the two inputs differently there, so the
assertion was wrong for that platform, not the paths_eq logic. CI caught
it on build (macos-latest).

Agentflare-Agent: claude-code_2-1-217_agent
Agentflare-Branch: feat/worktree-audit-294

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
crates/agentflare-backend/src/claim.rs (1)

46-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

TTL lookup duplicated across crates — risk of config drift.

This mirrors src/claims.rs::ttl_secs(), which reads the same AGENTFLARE_CLAIM_TTL_SECS env var but falls back to a different-looking constant (DEFAULT_TTL_SECS) vs. the hardcoded 1800 here. Now that default_ttl_secs() is pub, consider having claims::ttl_secs() delegate to this function (or vice versa) so the two claim subsystems can't silently diverge if one fallback is changed without the other.

♻️ Suggested consolidation direction
-pub fn ttl_secs() -> i64 {
-    std::env::var("AGENTFLARE_CLAIM_TTL_SECS")
-        .ok()
-        .and_then(|s| s.parse::<u64>().ok())
-        .unwrap_or(DEFAULT_TTL_SECS) as i64
-}
+pub fn ttl_secs() -> i64 {
+    agentflare_backend::claim::default_ttl_secs()
+}
🤖 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 `@crates/agentflare-backend/src/claim.rs` around lines 46 - 51, Consolidate the
duplicated AGENTFLARE_CLAIM_TTL_SECS lookup by making claims::ttl_secs() and
default_ttl_secs() share a single implementation and fallback constant. Update
the other function to delegate to the chosen canonical helper, preserving the
existing parsed environment value and fallback behavior so future changes cannot
diverge.
🤖 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.

Inline comments:
In `@crates/flare-git-core/src/shell.rs`:
- Around line 209-236: Correct
paths_eq_matches_case_and_separator_variants_on_windows_and_macos by separating
platform-specific assertions: keep the drive-letter and separator-variant paths
under Windows, and use forward-slash paths differing only in case for macOS.
Preserve the existing case-sensitive-platform assertion and unrelated-path
check.
- Around line 41-53: Update agentflare_shims_dir() to resolve the home directory
through crate::paths::home() instead of calling dirs::home_dir() directly,
preserving the existing .agentflare/shims path construction and ensuring
AGENTFLARE_HOME_OVERRIDE is honored.

In `@crates/flare-git-core/src/worktree.rs`:
- Around line 491-498: Update the WalkDir traversal in audit_orphans to process
entries individually instead of collecting into Result<Vec<_>, _>. Skip
unreadable or errored entries while continuing to inspect valid entries,
preserving any orphaned worktrees discovered so partial results are returned
rather than immediately returning the current orphans.
- Around line 548-573: Update gc_orphans to check the Result from
snapshot_before before calling remove_dir_all. If snapshot creation fails,
report the failure for that orphan and continue without deleting its worktree;
only proceed with removal after a successful snapshot.

In `@src/cli/git.rs`:
- Around line 448-451: Move the repository-root doc comment so it directly
documents resolve_repo_root, and leave claimed_sequence_ids documented only by
its own “Build set of claimed item sequence_ids from the DB” description. Ensure
neither function retains a misleading or misplaced doc comment.

---

Nitpick comments:
In `@crates/agentflare-backend/src/claim.rs`:
- Around line 46-51: Consolidate the duplicated AGENTFLARE_CLAIM_TTL_SECS lookup
by making claims::ttl_secs() and default_ttl_secs() share a single
implementation and fallback constant. Update the other function to delegate to
the chosen canonical helper, preserving the existing parsed environment value
and fallback behavior so future changes cannot diverge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e0fa14d-a50b-449e-9763-5de09ab66652

📥 Commits

Reviewing files that changed from the base of the PR and between 049bbee and 65b6d32.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/agentflare-backend/src/claim.rs
  • crates/flare-git-core/Cargo.toml
  • crates/flare-git-core/src/shell.rs
  • crates/flare-git-core/src/worktree.rs
  • src/cli/git.rs

Comment on lines +41 to +53
/// `~/.agentflare/shims` -- the PATH-shim install dir (mirrored here since
/// this crate can't depend on the main `agentflare` crate's `shim_install`
/// module). Must be excluded from `git_binary()`'s search the same way
/// `self_dir` is: `ensure_on_path` (`src/cli/git.rs`) prepends this dir to
/// the user's persistent PATH, so an unfiltered search resolves straight
/// back to the `git` PATH shim -- which classifies `worktree` as
/// always-deny (see `classify.rs`), making agentflare's OWN worktree
/// creation (`create_worktree`, called from the `item` claim flow)
/// self-deadlock silently: the shim's denial looks like an ordinary git
/// error to the soft-fail-on-error caller, so no error ever surfaces.
fn agentflare_shims_dir() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".agentflare").join("shims"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the home() helper used by shims_dir() in src/cli/git.rs
rg -nP -C5 '\bfn\s+home\s*\(' src/cli/git.rs
ast-grep run --pattern 'fn home($$$) { $$$ }' --lang rust src/cli/git.rs

Repository: getappz/agentflare

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files 'crates/flare-git-core/src/shell.rs' 'src/cli/git.rs'

printf '\n== shell.rs slice ==\n'
sed -n '1,260p' crates/flare-git-core/src/shell.rs | nl -ba | sed -n '1,260p'

printf '\n== git.rs slice around shims_dir/home ==\n'
rg -n -C 8 '\bfn\s+home\s*\(|\bfn\s+shims_dir\s*\(' src/cli/git.rs

printf '\n== git.rs around matching lines ==\n'
sed -n '170,240p' src/cli/git.rs | nl -ba | sed -n '1,120p'

Repository: getappz/agentflare

Length of output: 281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== shell.rs (lines 1-260 with numbers) ==\n'
awk 'NR>=1 && NR<=260 { printf "%4d  %s\n", NR, $0 }' crates/flare-git-core/src/shell.rs

printf '\n== git.rs (lines 1-280 with numbers) ==\n'
awk 'NR>=1 && NR<=280 { printf "%4d  %s\n", NR, $0 }' src/cli/git.rs

printf '\n== search for home helper usages ==\n'
rg -n -C 3 '\bhome\(\)' src/cli/git.rs crates/flare-git-core/src/shell.rs

Repository: getappz/agentflare

Length of output: 24247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== home() definition search ==\n'
rg -n -C 6 '\bfn\s+home\s*\(' src crates

printf '\n== paths_eq test search ==\n'
rg -n -C 6 '\bpaths_eq\b|agentflare_shims_dir_is_excludable_from_git_binary_search' crates/flare-git-core/src/shell.rs src

printf '\n== home helper files ==\n'
git ls-files | rg '(^|/)(paths|home)\.rs$|src/paths\.rs|src/.*home.*\.rs'

Repository: getappz/agentflare

Length of output: 6309


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== AGENTFLARE_HOME_OVERRIDE usages ==\n'
rg -n -C 4 'AGENTFLARE_HOME_OVERRIDE|home\(\)' src crates

printf '\n== call sites of git_binary / run_in / run_in_opt ==\n'
rg -n -C 4 '\bgit_binary\(\)|\brun_in\(|\brun_in_opt\(|\brun_in_ok\(' src crates

Repository: getappz/agentflare

Length of output: 50375


Mirror AGENTFLARE_HOME_OVERRIDE when building the shims path. agentflare_shims_dir() uses dirs::home_dir() directly, while the rest of the app resolves home through crate::paths::home(). That mismatch can make git_binary() miss ~/.agentflare/shims under temp-home/override-based runs and let the shim back into PATH.

🤖 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 `@crates/flare-git-core/src/shell.rs` around lines 41 - 53, Update
agentflare_shims_dir() to resolve the home directory through
crate::paths::home() instead of calling dirs::home_dir() directly, preserving
the existing .agentflare/shims path construction and ensuring
AGENTFLARE_HOME_OVERRIDE is honored.

Comment thread crates/flare-git-core/src/shell.rs
Comment thread crates/flare-git-core/src/worktree.rs Outdated
Comment thread crates/flare-git-core/src/worktree.rs
Comment thread src/cli/git.rs Outdated
- audit_orphans: filter_map instead of collect::<Result<Vec<_>,_>>() -- one
  unreadable subdirectory no longer aborts the whole scan and silently
  reports 'no orphans found'.
- gc_orphans: abort deletion of an orphan if its pre-delete snapshot fails,
  instead of discarding the error and deleting anyway with no recovery
  point.
- claimed_sequence_ids/resolve_repo_root: fix a doc comment that ended up
  describing the wrong function after an earlier insertion.

Agentflare-Agent: claude-code_2-1-217_agent
Agentflare-Branch: feat/worktree-audit-294
@getappz
getappz merged commit 5743b32 into master Jul 22, 2026
17 checks passed
@getappz
getappz deleted the feat/worktree-audit-294 branch July 22, 2026 12:08
getappz added a commit that referenced this pull request Jul 24, 2026
Audited every place the codebase excludes a shim's own directory from
PATH before resolving the real binary, to confirm the git_binary()
self-deny bug class (PR #304) isn't still reachable elsewhere.

Found one more: path_without_shim_dir() (shared exec plumbing used by
both the flare-git-shim and agentflare-shim binaries) filtered shim_dir
with a byte-equal PathBuf comparison. On Windows/macOS a case or / vs mismatch between shim_dir and PATH would leave the shim's own directory
in the filtered PATH, so run_real()'s which::which_in could resolve
back to the shim itself -- the same self-deny/recursion risk git_binary()
was fixed for, just in the shim's own passthrough path instead of
agentflare's internal git calls. The existing FLARE_GIT_SHIM_DEPTH
backstop would cap the blast radius, but this closes the actual gap
rather than relying on the backstop.

Ports the same paths_eq() normalization already proven in
flare_git_core::shell::git_binary() (duplicated rather than
cross-crate-depended on: agentflare-shim is deliberately dependency-
light generic plumbing, not git-specific).

No other self-exclusion filter sites exist in the codebase (grepped for
every filter(...) over PATH entries).

Agentflare-Agent: claude-code_2-1-218_agent
Agentflare-Branch: fix/claim-worktree-error-visibility
getappz added a commit that referenced this pull request Jul 24, 2026
…#318)

* fix(item): surface worktree creation failure reason in claim response

create_worktree() soft-failed on any git error and returned None, so
item_claim silently omitted worktree_path with no indication why. This
reads to the caller as an unexplained claim/worktree deadlock (hit live
in appz-cli, indistinguishable from a real circular dependency) instead
of the one known cause: a stale daemon's git_binary() PATH-shim
self-deny (PR #304's bug class recurring).

create_worktree now returns Result<PathBuf, String>; item_claim adds a
worktree_error field to the acquired response when creation fails, so
the failure is visible instead of silent.

Agentflare-Agent: claude-code_2-1-218_agent
Agentflare-Branch: fix/claim-worktree-error-visibility

* fix(agentflare-shim): case/separator-normalize shim-dir PATH exclusion

Audited every place the codebase excludes a shim's own directory from
PATH before resolving the real binary, to confirm the git_binary()
self-deny bug class (PR #304) isn't still reachable elsewhere.

Found one more: path_without_shim_dir() (shared exec plumbing used by
both the flare-git-shim and agentflare-shim binaries) filtered shim_dir
with a byte-equal PathBuf comparison. On Windows/macOS a case or / vs mismatch between shim_dir and PATH would leave the shim's own directory
in the filtered PATH, so run_real()'s which::which_in could resolve
back to the shim itself -- the same self-deny/recursion risk git_binary()
was fixed for, just in the shim's own passthrough path instead of
agentflare's internal git calls. The existing FLARE_GIT_SHIM_DEPTH
backstop would cap the blast radius, but this closes the actual gap
rather than relying on the backstop.

Ports the same paths_eq() normalization already proven in
flare_git_core::shell::git_binary() (duplicated rather than
cross-crate-depended on: agentflare-shim is deliberately dependency-
light generic plumbing, not git-specific).

No other self-exclusion filter sites exist in the codebase (grepped for
every filter(...) over PATH entries).

Agentflare-Agent: claude-code_2-1-218_agent
Agentflare-Branch: fix/claim-worktree-error-visibility

* fix(agentflare-shim): satisfy fmt + move test mod to end of file

cargo fmt wanted the multi-line closure/assert wrapped differently.
clippy::items_after_test_module flagged tool_name_from_exe/run_real
being defined after the #[cfg(test)] mod tests block -- moved the test
module to the end of the file, which also matches convention
everywhere else in this codebase.

Agentflare-Agent: claude-code_2-1-218_agent
Agentflare-Branch: fix/claim-worktree-error-visibility

* test(item): cover the worktree_error contract end to end

Addresses CodeRabbit nitpick on PR #318: the create_worktree failure
test only asserted an error existed, not its content, and item_claim
had no regression test for the failure path at all (only success).

- create_worktree_soft_fails_on_bad_git now asserts the error names the
  failing item and carries the underlying git failure detail, not just
  the format-string prefix.
- New item_claim_response_includes_worktree_error_instead_of_silently_omitting_it
  claims an item against a non-repo worktree_repo_root_override and
  asserts worktree_path is absent while worktree_error is present and
  non-empty.

Agentflare-Agent: claude-code_2-1-218_agent
Agentflare-Branch: fix/claim-worktree-error-visibility
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