Skip to content

fix: preserve subdirectory position on wt remove#3344

Merged
max-sixty merged 1 commit into
mainfrom
fix/issue-3343
Jul 1, 2026
Merged

fix: preserve subdirectory position on wt remove#3344
max-sixty merged 1 commit into
mainfrom
fix/issue-3343

Conversation

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

Problem

When switching worktrees, wt preserves the user's subdirectory position — if you're in apps/gateway/ and switch, you land in apps/gateway/ of the target worktree (implemented for #937). wt remove did not: it always landed at the destination worktree root, so a user working deep in a monorepo sub-tree had to manually cd back after every removal.

Reported in #3343.

Root cause

prepare_remove_directory_change in src/output/handlers.rs unconditionally cd'd to the destination worktree root (main_path), ignoring the user's position within the worktree being removed. Switch, by contrast, routes its cd through resolve_subdir_in_target, which maps the relative subpath onto the target.

Solution

Reuse the existing resolve_subdir_in_target helper in the remove path. Because prepare_remove_directory_change runs before the git removal, the current directory still exists inside the worktree being removed, so std::env::current_dir() yields the actual sub-position. When the equivalent subdirectory exists in the destination it becomes the cd target; otherwise it falls back to the root (and to the root if the cwd can't be read). The check on the destination is read-only (is_dir()), so there's no data-safety impact.

wt merge lands via the same handler, so it gains the same subdirectory preservation — a consistency win with switch.

This mirrors the always-on behavior chosen for switch in #937 (no new flag/config), keeping the two commands consistent.

Testing

Added two integration tests in tests/integration_tests/directives.rs, alongside the existing switch subdir tests:

  • test_remove_preserves_subdir — removing from feature/apps/gateway/ when main/apps/gateway/ exists lands the cd directive in main/apps/gateway/. Fails before the fix (lands at main/), passes after.
  • test_remove_falls_back_to_root_when_subdir_missing — when the subdir is absent in the destination, the cd directive lands at the main worktree root.

Verified the reproduction test fails on the unfixed code and passes with the fix. The directives::, remove::, and merge:: integration modules all pass (262 tests).


Closes #3343 — automated triage

When switching, wt preserves the user's subdirectory position (e.g.
apps/gateway/ in a monorepo). wt remove landed at the destination
worktree root instead, forcing a manual cd back after every removal.

Mirror the switch behavior: when the removed worktree is the current
one, compute the cwd's position relative to the removed worktree root
and cd into the equivalent subdirectory of the destination if it
exists, falling back to the root otherwise. Reuses the existing
resolve_subdir_in_target helper. Since wt merge lands via the same
handler, it gains the same subdirectory preservation.

Closes #3343

Co-Authored-By: Claude <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed as a self-authored triage PR (reviewer role). The fix cleanly reuses the existing resolve_subdir_in_target helper rather than reimplementing the subpath mapping, mirrors the wt switch behavior it's modeled on, and is covered by two integration tests (subdir-present and fallback-to-root). An independent correctness pass over the cd-target computation — including the cwd == worktree root, subdir, and bare-repo (main_path == current_path) edges — surfaced nothing: the resolved target is always under main_path (the destination worktree, distinct from the removed one), and the destination check is read-only (is_dir()), so there's no new deletion behavior.

Flagging for a human look per the repo's data-loss review policy (review-pr.md): this edits src/output/handlers.rs, which lives on the wt remove surface. The change runs before the git removal and only computes where the shell cds to afterward, so it doesn't touch the removal path itself — but a change on that file is maintainer's-eyes-only by policy. @max-sixty, a quick sanity check when you have a moment.

@max-sixty max-sixty merged commit 6753528 into main Jul 1, 2026
38 of 47 checks passed
@max-sixty max-sixty deleted the fix/issue-3343 branch July 1, 2026 07:41
@max-sixty

Copy link
Copy Markdown
Owner

great! can we consider unifying these functions so, for example, we get the same behavior with wt merge — basically anything switching paths?

worktrunk-bot added a commit that referenced this pull request Jul 1, 2026
max-sixty asked (in #3344) whether the subdirectory-preservation logic
could be unified so every path-switching command behaves the same. `wt
switch` and `wt remove`/`wt merge` already share `resolve_subdir_in_target`
(merge lands via the same handler). The one remaining path that hand-rolled
its own `dest.join(relative)` was `wt step relocate`.

Route relocate's shell `cd` through the same helper, keeping its "only cd
when the user is inside the moving worktree" guard. This also gains the
helper's symlink canonicalization (relocate previously did a raw
non-canonical `strip_prefix`) and the read-only `is_dir()` fallback (a no-op
here since `git worktree move` carries the whole subtree, but keeps behavior
identical across commands).

Adds `test_relocate_preserves_subdir`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@worktrunk-bot

Copy link
Copy Markdown
Collaborator Author

Good call. I traced every site that emits a shell cd to move between worktrees:

Command Subdir helper
wt switch / switch --create resolve_subdir_in_target
wt remove / wt merge resolve_subdir_in_target
wt step relocate hand-rolled dest.join(relative)

So wt merge is actually already covered — it lands via the same handler as wt remove, so this PR gave it subdir preservation for free. The one path still hand-rolling its own logic was wt step relocate.

Unified it in #3346: relocate now routes through the same resolve_subdir_in_target helper (keeping its "only cd if you're inside the moving worktree" guard), which also picks up the helper's symlink canonicalization that the hand-rolled version lacked.

@max-sixty max-sixty mentioned this pull request Jul 2, 2026
max-sixty added a commit that referenced this pull request Jul 2, 2026
Release v0.65.0. Version bump plus the 0.65.0 changelog section.

**User-facing changes in this release:**

- **Picker `alt-x` flashes why a worktree wasn't removed** (#3336,
#3350) — the keep/failure reason now appears in the picker header
instead of only draining to stderr on exit.
- **`-vv` diagnostics consolidate on `diagnostic.md`, led by the
performance profile** (#3329). This entry's bullet was previously
misfiled under the already-released `0.63.0` section (the PR merged
after `0.64.0` was tagged); this release moves it into `0.65.0`, where
the change actually ships, and drops a misleading "removed
`profile.txt`" clause for a file that never existed.
- **`wt remove` preserves your subdirectory position** (#3344, closes
#3343, thanks @caillou for reporting).

**Also included:** a fix to the release skill's reporter-finding grep —
`sort -un` on `#`-prefixed lines collapsed the whole issue-reference
list to a single entry (every line sorts as numeric 0), which nearly
dropped @caillou's credit. Now sorts on the numeric field after `#`.

The nightly cross-platform suite (full-tests linux/macos/windows,
feature-powerset, release-target, nix-flake, minimal-versions) passed
green on the cut-from tip before this PR was opened.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-fix Automated CI fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wt remove should probably preserve the current working directory

2 participants