Skip to content

update-20260103b--impl-tmux-worktree-cd--codex#176

Open
tamakiii wants to merge 1 commit intoupdate-20260103bfrom
update-20260103b--impl-tmux-worktree-cd--codex
Open

update-20260103b--impl-tmux-worktree-cd--codex#176
tamakiii wants to merge 1 commit intoupdate-20260103bfrom
update-20260103b--impl-tmux-worktree-cd--codex

Conversation

@tamakiii
Copy link
Owner

@tamakiii tamakiii commented Jan 8, 2026

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

• 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.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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-cd that 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 cd commands 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
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
printf 'tmux-worktree-cd: %s\n' "$*" >&2
printf 'worktree-cd: %s\n' "$*" >&2

Copilot uses AI. Check for mistakes.
label="detached"
} else if (label=="") {
label=head
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
}
}
if (label=="") {
n=split(path, parts, "/")
label=""
for (j=n; j>=1 && label==""; j--) {
if (parts[j] != "") {
label=parts[j]
}
}
if (label=="") {
label="unknown"
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +86
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
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
bind-key C-b copy-mode

# git worktree helper
bind-key W run-shell "~/.config/tmux/worktree-cd"
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
bind-key W run-shell "~/.config/tmux/worktree-cd"
bind-key W run-shell "worktree-cd"

Copilot uses AI. Check for mistakes.
bind-key C-b copy-mode

# git worktree helper
bind-key W run-shell "~/.config/tmux/worktree-cd"
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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"'

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +89
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
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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}")

Copilot uses AI. Check for mistakes.
esac
done

tmux display-message "Switched window to $target_path"
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
tmux display-message "Switched window to $target_path"
safe_target_path=${target_path//'#'/##}
tmux display-message "Switched window to $safe_target_path"

Copilot uses AI. Check for mistakes.
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