Tmux 会话恢复常见问题排查

Tmux 故障排查

问题 1:恢复后每个面板显示 %

现象

tmux-resurrect 恢复会话后,每个新建的面板中显示一个 % 字符。

原因

zsh 的 PROMPT_SP(也叫 PROMPT_EOL_MARK)选项。当终端输出不以换行符结尾时, zsh 自动在行末显示 % 来标记”不完整的行(partial line)”。

tmux 恢复时创建新面板/窗口,zsh 在新面板中启动,由于初始状态下终端没有 换行输出,触发了 PROMPT_SP

解决

~/.zshrc 中添加:

1
unsetopt PROMPT_SP

或者:

1
PROMPT_EOL_MARK=''

问题 2:重启后无法恢复 tmux 会话

现象

系统重启后,之前的 tmux 会话没有自动恢复。

原因分析

分两种情况:

情况 A:没有启动 tmux 系统重启后没有 tmux 进程运行。需要手动启动 tmux 或配置开机自启。 @continuum-restore on 只在 tmux 服务启动时触发恢复,不会自己启动 tmux。

情况 B:启动了 tmux 但未恢复 可能原因:

  1. @continuum-restore 未设置为 on
  2. 保存文件的 last 符号链接损坏
  3. ~/tmux_no_auto_restore 停用文件存在
  4. continuum.tmux 中的 set -x 干扰了恢复流程
  5. 插件未正确安装(检查 ~/.tmux/plugins/tpm_log.txt

解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 确认配置
grep '@continuum-restore' ~/.tmux.conf
grep '@continuum-boot' ~/.tmux.conf

# 2. 检查保存文件
ls -la ~/.local/share/tmux/resurrect/last

# 3. 手动测试恢复
~/.tmux/plugins/tmux-resurrect/scripts/restore.sh

# 4. 如果需要开机自启
set -g @continuum-boot on # 添加到 .tmux.conf
# 重载配置后自动创建 systemd 服务
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 startnode → 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 0

is_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
}

# 通过 tty + stat 列找前台进程
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
}

# 回退:最深的非 shell 叶子进程
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 中运行如 sleep 300
# 获取该 pane 的 PID
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
# 应该输出: "sleep 300"

# 手动保存并检查
~/.tmux/plugins/tmux-resurrect/scripts/save.sh quiet
grep "^pane" ~/.local/share/tmux/resurrect/last
# pane_full_command 列应该不为空

问题 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 会话"

Tmux 会话恢复常见问题排查
https://tingfeng347.github.io/2026/05/29/Tmux 会话恢复常见问题排查/
作者
Tingfeng
发布于
2026年5月29日
许可协议