Tmux 故障排查 问题 1:恢复后每个面板显示 % 现象 tmux-resurrect 恢复会话后,每个新建的面板中显示一个 % 字符。
原因 zsh 的 PROMPT_SP(也叫 PROMPT_EOL_MARK)选项。当终端输出不以换行符结尾时, zsh 自动在行末显示 % 来标记”不完整的行(partial line)”。
tmux 恢复时创建新面板/窗口,zsh 在新面板中启动,由于初始状态下终端没有 换行输出,触发了 PROMPT_SP。
解决 在 ~/.zshrc 中添加:
或者:
问题 2:重启后无法恢复 tmux 会话 现象 系统重启后,之前的 tmux 会话没有自动恢复。
原因分析 分两种情况:
情况 A:没有启动 tmux 系统重启后没有 tmux 进程运行。需要手动启动 tmux 或配置开机自启。 @continuum-restore on 只在 tmux 服务启动时触发恢复,不会自己启动 tmux。
情况 B:启动了 tmux 但未恢复 可能原因:
@continuum-restore 未设置为 on
保存文件的 last 符号链接损坏
~/tmux_no_auto_restore 停用文件存在
continuum.tmux 中的 set -x 干扰了恢复流程
插件未正确安装(检查 ~/.tmux/plugins/tpm_log.txt)
解决 1 2 3 4 5 6 7 8 9 10 11 12 13 14 grep '@continuum-restore' ~/.tmux.conf grep '@continuum-boot' ~/.tmux.confls -la ~/.local/share/tmux/resurrect/last ~/.tmux/plugins/tmux-resurrect/scripts/restore.shset -g @continuum-boot on tmux source-file ~/.tmux.conf
问题 3:恢复后运行的程序/命令丢失 现象 tmux 会话布局恢复正确,但每个面板中原来运行的命令(如 python, node, nvim, npm 等)没有恢复,只有空白的 shell。
原因 tmux-resurrect 的核心限制:只能恢复面板的初始进程 。
保存策略通过查找 pane PID 的直接子进程 来获取 pane_full_command。 对于交互式在 shell 中启动的命令:
如果命令正在前台运行 → 它是 shell 的子进程,策略可以捕获
如果面板空闲(没有前台命令)→ pane_full_command 为空,无法恢复
如果命令嵌套多级(如 npm start → node → worker)→ 默认策略可能捕获错误
此外,即使捕获到了完整命令,还需要 @resurrect-processes 白名单匹配。 默认白名单只有:vi vim view nvim emacs man less more tail top htop irssi weechat mutt。
解决 步骤 1:使用 deep_leaf 策略
在 ~/.tmux.conf 中:
set -g @resurrect-save-command-strategy 'deep_leaf'
策略脚本 ~/.tmux/plugins/tmux-resurrect/save_command_strategies/deep_leaf.sh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #!/usr/bin/env bash PANE_PID="$1 " [ -z "$PANE_PID " ] && exit 0is_shell () { local name="$1 " case "$name " in zsh|bash|fish|sh|dash|ksh|tcsh|csh|rbash|rzsh|tmux:*) return 0 ;; *) return 1 ;; esac }full_command_from_pid () { local pid="$1 " if [ -r "/proc/$pid /cmdline" ]; then local cmd cmd=$(cat "/proc/$pid /cmdline" | xargs -0 bash -c 'printf "%q " "$0" "$@"' 2>/dev/null) echo "${cmd% } " return 0 fi return 1 }find_foreground_by_tty () { local tty tty =$(ps -o tty = -p "$PANE_PID " 2>/dev/null | tr -d ' ' ) [ -z "$tty " ] || [ "$tty " = "?" ] && return 1 local best_pid="" best_depth=999 local pid ppid stat comm while IFS=' ' read -r pid ppid stat comm ; do [[ "$stat " != *"+" * ]] && continue is_shell "$comm " && continue [ "$pid " = "$PANE_PID " ] && continue local depth=0 cur=$pid while [ "$cur " != "$PANE_PID " ] && [ $depth -lt 30 ]; do depth=$((depth + 1 )) cur=$(ps -o ppid= -p "$cur " 2>/dev/null | tr -d ' ' ) [ -z "$cur " ] && break done if [ "$cur " = "$PANE_PID " ] && [ $depth -lt $best_depth ]; then best_depth=$depth best_pid=$pid fi done < <(ps -o pid=,ppid=,stat =,comm = -t "$tty " 2>/dev/null) [ -n "$best_pid " ] && echo "$best_pid " && return 0 return 1 }find_deepest_leaf () { local all_descendants="" queue="$PANE_PID " pid for pid in $queue ; do local children=$(pgrep -P "$pid " 2>/dev/null) for child in $children ; do all_descendants="$all_descendants $child " queue="$queue $child " done done local best_pid="" best_depth=0 for pid in $all_descendants ; do local comm =$(ps -o comm = -p "$pid " 2>/dev/null) is_shell "$comm " && continue local has_children=$(pgrep -P "$pid " 2>/dev/null | wc -l) [ "$has_children " -gt 0 ] && continue local depth=0 cur=$pid while [ "$cur " != "$PANE_PID " ] && [ $depth -lt 30 ]; do depth=$((depth + 1 )) cur=$(ps -o ppid= -p "$cur " 2>/dev/null | tr -d ' ' ) [ -z "$cur " ] && break done [ $depth -gt $best_depth ] && best_depth=$depth && best_pid=$pid done [ -n "$best_pid " ] && echo "$best_pid " && return 0 return 1 }main () { local target_pid target_pid=$(find_foreground_by_tty) [ -z "$target_pid " ] && target_pid=$(find_deepest_leaf) [ -n "$target_pid " ] && full_command_from_pid "$target_pid " && exit 0 exit 0 } main
步骤 2:配置恢复全部程序
set -g @resurrect-processes ':all:'
步骤 3:验证
1 2 3 4 5 6 7 8 9 10 11 12 PANE_PID=$(tmux display-message -p -F '#{pane_pid}' -t tingfeng-0:1.2) bash ~/.tmux/plugins/tmux-resurrect/save_command_strategies/deep_leaf.sh $PANE_PID ~/.tmux/plugins/tmux-resurrect/scripts/save.sh quiet grep "^pane" ~/.local/share/tmux/resurrect/last
问题 4:continuum.tmux 中的 set -x 现象 每次 tmux 启动或重载配置文件时,输出大量 + 开头的 bash 调试信息。
原因 ~/.tmux/plugins/tmux-continuum/continuum.tmux 第 3 行有 set -x, 当 TPM 加载该插件脚本时,bash 进入调试模式并打印所有执行命令。
解决 删除文件中第 3 行的 set -x:
1 sed -i '/^set -x$/d' ~/.tmux/plugins/tmux-continuum/continuum.tmux
注意:更新插件后可能恢复,需要再次删除。
问题 5:切换到 zsh 后旧的 fish 会话兼容性 现象 从 fish 切换到 zsh 作为默认 shell 后,恢复的会话中面板运行的是 zsh(正确), 但 pane_command 字段在保存文件中仍可能显示 fish(如果保存时用的是 fish)。
影响 不影响恢复。恢复时面板使用 default-shell 配置启动,不依赖保存文件中的 pane_command 字段。pane_command 仅用于程序恢复匹配。
快速诊断脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/bin/bash echo "=== Tmux 诊断 ===" echo "" echo "1. 配置文件" echo " default-shell: $(tmux show-option -gqv default-shell) " echo " @continuum-restore: $(tmux show-option -gqv @continuum-restore) " echo " @continuum-boot: $(tmux show-option -gqv @continuum-boot) " echo " @resurrect-save-command-strategy: $(tmux show-option -gqv @resurrect-save-command-strategy) " echo " @resurrect-processes: $(tmux show-option -gqv @resurrect-processes) " echo "" echo "2. 保存文件" if [ -L ~/.local/share/tmux/resurrect/last ]; then echo " last -> $(readlink ~/.local/share/tmux/resurrect/last) " echo " 文件大小: $(stat -c%s ~/.local/share/tmux/resurrect/last) bytes" else echo " last 符号链接不存在!" fi echo "" echo "3. systemd 自启状态" systemctl --user is-enabled tmux.service 2>/dev/null || echo " 未启用" systemctl --user is-active tmux.service 2>/dev/null || echo " 未运行" echo "" echo "4. 当前会话" tmux list-sessions 2>/dev/null || echo " 无 tmux 会话"