Skip to content

fix(worktree): reconcile detached-HEAD worktrees, run prune on clean, clamp zero timeout#6051

Merged
bug-ops merged 1 commit into
mainfrom
fix/5936-worktree-reconcile-detached-head
Jul 11, 2026
Merged

fix(worktree): reconcile detached-HEAD worktrees, run prune on clean, clamp zero timeout#6051
bug-ops merged 1 commit into
mainfrom
fix/5936-worktree-reconcile-detached-head

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Three related bug fixes in crates/zeph-worktree, spec-governed by specs/063-worktree-subsystem/spec.md and srs.md:

  • WorktreeManager::reconcile() silently dropped detached-HEAD worktrees because the git worktree list --porcelain parser only flushed a block when both a worktree <path> line and a branch line were present. Detached worktrees emit detached instead of branch, so they became permanently invisible to zeph worktree list/zeph worktree clean. The parser now flushes whenever worktree <path> is seen, and assigns a new DETACHED_BRANCH_SENTINEL ("(detached HEAD)") as branch_name when no branch line is present. The sentinel contains a space, which git check-ref-format --branch rejects, so it can never collide with a real branch name — including on a worktree foreign to zeph, since reconcile() enumerates all worktrees in the repo, not just zeph-managed ones.
  • zeph worktree clean never called git worktree prune after removing stale entries, per spec-063 FR-CLEANUP-04. Added WorktreeManager::prune() and wired it into the clean command after the removal loop.
  • DefaultGitRunner did not clamp git_timeout_secs = 0 itself; the clamp was duplicated ad hoc at two call sites, and the crate's derived Default impl bypassed both (zero-initializing the Duration field). The clamp now lives in DefaultGitRunner::new()/with_timeout(), the ad hoc call-site clamps were removed, and Default is now a manual impl delegating to new().

A P3 follow-up issue will be filed separately for bare-repo worktrees also falling through to the detached sentinel (mislabeling, not a spec violation — spec-063 doesn't cover bare worktrees) — that gap was found during review and intentionally deferred out of this PR's scope.

Closes #5936
Closes #5937
Closes #5939

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 12828 passed, 0 failed
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • RUSTFLAGS="-D warnings" cargo check --workspace --all-targets --features desktop,ide,server,chat,pdf,scheduler --locked
  • cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • New real-git integration tests in crates/zeph-worktree/tests/real_git_integration.rs (uses the real git binary, not a fake runner): prune clears a manually-removed worktree's leftover admin entry, full clean pipeline (reconcile+remove+prune), zero-timeout clamp against a real git invocation, and the sentinel's ref-name invalidity via git check-ref-format
  • .local/testing/playbooks/worktree.md and .local/testing/coverage-status.md updated in the main repo

… clamp zero timeout

reconcile() previously dropped any worktree whose git worktree list
--porcelain block lacked a branch line (detached HEAD), making such
entries permanently invisible to list/clean. The porcelain parser now
flushes on the worktree <path> line alone and assigns a sentinel
branch_name that is provably not a valid git ref, so it can never
collide with a real (including foreign, non-zeph) branch (#5936).

zeph worktree clean now runs git worktree prune once after removing
stale entries, per spec-063 FR-CLEANUP-04 (#5937).

DefaultGitRunner now clamps git_timeout_secs=0 to a 1-second floor
internally, so the invariant holds regardless of caller; the two
ad hoc .max(1) call sites and the bypassable derived Default impl
are removed (#5939).
@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes labels Jul 11, 2026
@bug-ops bug-ops enabled auto-merge (squash) July 11, 2026 16:38
@github-actions github-actions Bot added bug Something isn't working size/XL Extra large PR (500+ lines) labels Jul 11, 2026
@bug-ops bug-ops merged commit f0a9e90 into main Jul 11, 2026
43 checks passed
@bug-ops bug-ops deleted the fix/5936-worktree-reconcile-detached-head branch July 11, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant