update-20260103b--impl-tmux-worktree-cd--codex#176
update-20260103b--impl-tmux-worktree-cd--codex#176tamakiii wants to merge 1 commit intoupdate-20260103bfrom
Conversation
• Added a tmux helper to pick a git worktree (fzf with select-1 fallback) and retarget the current window: sets the window default-path and sends cd to panes running a shell, leaving other panes untouched (os/mac/.config/ tmux/worktree-cd). Bound the helper to prefix + W in os/mac/.config/tmux/tmux.conf (reload with your existing prefix + R binding). Testing: bash -n os/mac/.config/tmux/worktree-cd. Next steps: reload tmux config, then in a repo window hit prefix + W to choose the worktree; restart non- shell panes like hx if you want them in the new path.
There was a problem hiding this comment.
Pull request overview
This PR adds a tmux helper script for git worktree navigation with fuzzy finding capabilities. The script allows users to quickly switch a tmux window's working directory to a different git worktree, automatically updating shell panes while leaving non-shell panes untouched.
Key Changes:
- New bash script
worktree-cdthat lists git worktrees using fzf (with fallback), validates selection, and updates tmux window settings - Tmux keybinding (prefix + W) added to invoke the worktree switcher
- Intelligent pane detection that only sends
cdcommands to shell panes
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| os/mac/.config/tmux/worktree-cd | New bash script implementing git worktree selection and tmux window/pane directory switching with fzf integration |
| os/mac/.config/tmux/tmux.conf | Adds keybinding for prefix + W to run the worktree-cd helper script |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| set -euo pipefail | ||
|
|
||
| err() { | ||
| printf 'tmux-worktree-cd: %s\n' "$*" >&2 |
There was a problem hiding this comment.
The script name in the error messages is inconsistent with the actual script filename. The error function uses 'tmux-worktree-cd' while the script is named 'worktree-cd'. This could cause confusion when debugging. Consider using 'worktree-cd' or adjusting the filename to match.
| printf 'tmux-worktree-cd: %s\n' "$*" >&2 | |
| printf 'worktree-cd: %s\n' "$*" >&2 |
| label="detached" | ||
| } else if (label=="") { | ||
| label=head | ||
| } |
There was a problem hiding this comment.
The awk script doesn't handle the case where a worktree has no HEAD information (bare worktree or corrupted state). If both branch and head are empty and detached is 0, the label will be empty which could cause an unhelpful display. Consider adding a fallback to use the path basename or a default label like 'unknown' when label remains empty after all checks.
| } | |
| } | |
| if (label=="") { | |
| n=split(path, parts, "/") | |
| label="" | |
| for (j=n; j>=1 && label==""; j--) { | |
| if (parts[j] != "") { | |
| label=parts[j] | |
| } | |
| } | |
| if (label=="") { | |
| label="unknown" | |
| } | |
| } |
| escaped_path=$(printf "%q" "$target_path") | ||
|
|
||
| tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}" | while read -r pane_id pane_cmd; do | ||
| case "$pane_cmd" in | ||
| ""|zsh|bash|sh|fish|dash) | ||
| tmux send-keys -t "$pane_id" "cd -- $escaped_path" C-m |
There was a problem hiding this comment.
Potential shell injection vulnerability: the escaped_path uses printf "%q" which is bash-specific shell escaping, but it's then sent to tmux send-keys which injects it into potentially different shells (zsh, fish, dash, etc.). Different shells have different escaping requirements. For instance, fish uses different quoting rules than bash. Consider using single quotes around the path in the cd command instead, with proper handling of single quotes in the path itself.
| escaped_path=$(printf "%q" "$target_path") | |
| tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}" | while read -r pane_id pane_cmd; do | |
| case "$pane_cmd" in | |
| ""|zsh|bash|sh|fish|dash) | |
| tmux send-keys -t "$pane_id" "cd -- $escaped_path" C-m | |
| # Safely single-quote the target path for use in various shells. | |
| # This replaces each single quote ' with the sequence '\'' and then | |
| # we surround the result with single quotes in the cd command below. | |
| single_quoted_path=${target_path//\'/\'"\'"\'} | |
| tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}" | while read -r pane_id pane_cmd; do | |
| case "$pane_cmd" in | |
| ""|zsh|bash|sh|fish|dash) | |
| tmux send-keys -t "$pane_id" "cd -- '$single_quoted_path'" C-m |
| bind-key C-b copy-mode | ||
|
|
||
| # git worktree helper | ||
| bind-key W run-shell "~/.config/tmux/worktree-cd" |
There was a problem hiding this comment.
The path to the worktree-cd script is hardcoded as "~/.config/tmux/worktree-cd", which assumes the script is in a specific location. If the user's dotfiles are symlinked or stored elsewhere, this may not work. Consider using a relative path or the #{pane_current_path} variable to reference the script more portably.
| bind-key W run-shell "~/.config/tmux/worktree-cd" | |
| bind-key W run-shell "worktree-cd" |
| bind-key C-b copy-mode | ||
|
|
||
| # git worktree helper | ||
| bind-key W run-shell "~/.config/tmux/worktree-cd" |
There was a problem hiding this comment.
The script assumes the worktree-cd file is executable, but there's no verification or error handling if run-shell fails. If the script isn't executable or doesn't exist at the hardcoded path, tmux will fail silently or show a cryptic error. Consider adding error handling or documenting the requirement that the script must be executable.
| bind-key W run-shell "~/.config/tmux/worktree-cd" | |
| bind-key W if-shell 'test -x ~/.config/tmux/worktree-cd' 'run-shell ~/.config/tmux/worktree-cd' 'display-message "tmux: ~/.config/tmux/worktree-cd not found or not executable"' |
| tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}" | while read -r pane_id pane_cmd; do | ||
| case "$pane_cmd" in | ||
| ""|zsh|bash|sh|fish|dash) | ||
| tmux send-keys -t "$pane_id" "cd -- $escaped_path" C-m | ||
| ;; | ||
| esac | ||
| done |
There was a problem hiding this comment.
The while loop creates a subshell due to the pipe, which means any variable assignments or side effects inside the loop won't persist. While this doesn't affect the current implementation since each iteration is independent, this pattern can lead to subtle bugs if the code is modified later. Consider using process substitution with 'while read -r ... < <(tmux list-panes ...)' instead for better maintainability.
| tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}" | while read -r pane_id pane_cmd; do | |
| case "$pane_cmd" in | |
| ""|zsh|bash|sh|fish|dash) | |
| tmux send-keys -t "$pane_id" "cd -- $escaped_path" C-m | |
| ;; | |
| esac | |
| done | |
| while read -r pane_id pane_cmd; do | |
| case "$pane_cmd" in | |
| ""|zsh|bash|sh|fish|dash) | |
| tmux send-keys -t "$pane_id" "cd -- $escaped_path" C-m | |
| ;; | |
| esac | |
| done < <(tmux list-panes -t "$window_id" -F "#{pane_id} #{pane_current_command}") |
| esac | ||
| done | ||
|
|
||
| tmux display-message "Switched window to $target_path" |
There was a problem hiding this comment.
The tmux display-message "Switched window to $target_path" call passes the unescaped target_path directly into a tmux format string, which can interpret #() and related constructs and execute arbitrary shell commands. If a worktree path contains tmux format specifiers (for example with #(), selecting it here will cause tmux to run attacker-controlled commands when the message is displayed. To avoid this, ensure target_path is safely escaped or rendered in a context that does not interpret tmux format strings before being used in display-message, or avoid including untrusted path data in a tmux format at all.
| tmux display-message "Switched window to $target_path" | |
| safe_target_path=${target_path//'#'/##} | |
| tmux display-message "Switched window to $safe_target_path" |
• Added a tmux helper to pick a git worktree (fzf with select-1 fallback) and retarget the current window: sets
the window default-path and sends cd to panes running a shell, leaving other panes untouched (os/mac/.config/
tmux/worktree-cd).
Bound the helper to prefix + W in os/mac/.config/tmux/tmux.conf (reload with your existing prefix + R
binding).
Testing: bash -n os/mac/.config/tmux/worktree-cd.
Next steps: reload tmux config, then in a repo window hit prefix + W to choose the worktree; restart non-
shell panes like hx if you want them in the new path.