Summary
run_worktree's has_action set is missing repair, so git worktree repair <path> falls through to list mode. rtk runs git worktree list instead, prints the filtered list, and returns 0. The repair never runs and nothing says so.
Same masking class as #2497, but the cause is different: there the read path swallowed a git failure, here a write subcommand never reaches git at all.
Found through the Claude Code PreToolUse hook, which rewrites git worktree repair ... to rtk git worktree repair .... The agent read the list output as a successful repair and moved on with two still-broken worktrees.
Reproduce
$ git init -q A && cd A && git commit -q --allow-empty -m init
$ git worktree add -q -b wt A/.worktrees/wt
$ cd .. && mv A B && cd B # moving the repo breaks both gitdir pointers
$ cat B/.worktrees/wt/.git
gitdir: /abs/path/A/.git/worktrees/wt # stale, points at the old location
$ rtk git worktree repair /abs/path/B/.worktrees/wt
/abs/path/B 9d3c6f8 [main]
/abs/path/A/.worktrees/wt 9d3c6f8 [wt] prunable
$ echo $?
0
$ cat B/.worktrees/wt/.git
gitdir: /abs/path/A/.git/worktrees/wt # unchanged
$ git worktree repair /abs/path/B/.worktrees/wt
repair: gitdir incorrect: /abs/path/B/.git/worktrees/wt/gitdir
$ cat B/.worktrees/wt/.git
gitdir: /abs/path/B/.git/worktrees/wt # repaired
rtk 0.45.0, macOS.
Cause
src/cmds/git/git.rs:2143
let has_action = args.iter().any(|a| {
a == "add" || a == "remove" || a == "prune" || a == "lock" || a == "unlock" || a == "move"
});
git has eight worktree subcommands: add, list, lock, move, prune, remove, repair, unlock. Six are listed, list is the fallback, repair is the only one missing.
I verified the other six empirically: add, remove, remove --force, lock, unlock, move and prune all take effect. repair is the only broken one.
Two more defects at the same site
List mode drops the caller's flags. Line 2179 hardcodes the invocation:
cmd.args(["worktree", "list"]);
So git worktree list --porcelain returns the compact filtered format, not porcelain. Anything parsing that output gets a shape it never asked for:
$ rtk git worktree list --porcelain
/abs/path/B 9d3c6f8 [main] # not porcelain
This is arguably worse than the repair case, since porcelain exists precisely to be machine-read.
has_action matches the token in any position. args.iter().any(...) tests every argument, not the subcommand slot. A worktree whose directory is named move or remove selects the action branch regardless of the actual subcommand.
Fix shape
Workaround
exclude_commands = ["git worktree"] in the rtk config keeps the rest of the git proxying while removing the hazard.
Summary
run_worktree'shas_actionset is missingrepair, sogit worktree repair <path>falls through to list mode. rtk runsgit worktree listinstead, prints the filtered list, and returns 0. The repair never runs and nothing says so.Same masking class as #2497, but the cause is different: there the read path swallowed a git failure, here a write subcommand never reaches git at all.
Found through the Claude Code PreToolUse hook, which rewrites
git worktree repair ...tortk git worktree repair .... The agent read the list output as a successful repair and moved on with two still-broken worktrees.Reproduce
rtk 0.45.0, macOS.
Cause
src/cmds/git/git.rs:2143git has eight
worktreesubcommands:add,list,lock,move,prune,remove,repair,unlock. Six are listed,listis the fallback,repairis the only one missing.I verified the other six empirically:
add,remove,remove --force,lock,unlock,moveandpruneall take effect.repairis the only broken one.Two more defects at the same site
List mode drops the caller's flags. Line 2179 hardcodes the invocation:
So
git worktree list --porcelainreturns the compact filtered format, not porcelain. Anything parsing that output gets a shape it never asked for:This is arguably worse than the
repaircase, since porcelain exists precisely to be machine-read.has_actionmatches the token in any position.args.iter().any(...)tests every argument, not the subcommand slot. A worktree whose directory is namedmoveorremoveselects the action branch regardless of the actual subcommand.Fix shape
repairto thehas_actionset, or invert it: treat everything exceptlist(and no subcommand) as pass-through, so the next git release does not reopen this.["worktree", "list"], and skip filtering when a format flag such as--porcelainor-zis present.args[0]rather than anywhere inargs.Workaround
exclude_commands = ["git worktree"]in the rtk config keeps the rest of the git proxying while removing the hazard.