文章目录
- [手机远程连接 WSL2 中的 Codex CLI:Tailscale + Termius + OpenSSH + tmux 实战](#手机远程连接 WSL2 中的 Codex CLI:Tailscale + Termius + OpenSSH + tmux 实战)
-
- 一、为什么选择这套架构
- 二、准备条件
- [三、在 Windows 上启用 OpenSSH Server](#三、在 Windows 上启用 OpenSSH Server)
- [四、确认 Tailscale 网络](#四、确认 Tailscale 网络)
- [五、在 Termius 中添加 Windows 主机](#五、在 Termius 中添加 Windows 主机)
- [六、用 tmux 承载 Codex CLI](#六、用 tmux 承载 Codex CLI)
- [七、正确离开和重新进入 tmux](#七、正确离开和重新进入 tmux)
-
- [暂时离开,但让 Codex 继续运行](#暂时离开,但让 Codex 继续运行)
- 重新进入已有会话
- [八、解决 `already has an active writer`](#八、解决
already has an active writer) - 九、解决手机打字和粘贴困难
-
- [1. 使用 Termius Paste mode](#1. 使用 Termius Paste mode)
- [2. 把常用命令保存成 Snippets](#2. 把常用命令保存成 Snippets)
- [3. 配置 Termius Startup Command](#3. 配置 Termius Startup Command)
- 十、常见问题速查
- 十一、最终的日常使用流程
- 总结
- 参考资料
手机远程连接 WSL2 中的 Codex CLI:Tailscale + Termius + OpenSSH + tmux 实战
我最初的远程工作流是:在手机上连接 ChatGPT Desktop,再让桌面端打开 WSL2、进入 Ubuntu、启动 Codex CLI。它虽然能用,但路径太长,不仅慢,还多了一层对话中转。
后来我把链路改成了:
text
手机 Termius
│ SSH(端口 22)
▼
Tailscale 私有网络
│
▼
Windows OpenSSH Server
│ wsl.exe
▼
WSL2 Ubuntu
│ tmux
▼
Codex CLI
现在,手机可以直接操作电脑上 WSL2 里的 Codex CLI;即使手机锁屏、网络切换或 SSH 断开,Codex 仍能留在 tmux 中继续运行。重新连接后,只需附加到原来的 tmux 会话即可。
本文记录的是一套已经实际跑通的方案。示例中的发行版名称、用户名和项目路径需要替换成你自己的值。
一、为什么选择这套架构
这套方案由四部分组成:
- Tailscale:把手机和电脑放进同一个加密私有网络,无需把路由器的 SSH 端口暴露到公网。
- Windows OpenSSH Server:让 Termius 先通过 SSH 登录 Windows。
- WSL2 Ubuntu:Codex CLI 和开发环境实际运行的位置。
- tmux:把 Codex CLI 从 SSH 连接中解耦;SSH 断开,终端任务仍然存在。
这里并不是让 Termius 直接连接 WSL2 的 172.x.x.x 地址。WSL2 默认使用虚拟网络,其内部 IP 可能变化;让 Windows 作为稳定入口,再执行 wsl.exe 进入 Ubuntu,配置更简单,也更容易维护。
二、准备条件
开始前需要准备:
- 一台安装了 WSL2 的 Windows 电脑。
- WSL2 中已经安装并登录过 Codex CLI。
- 手机和电脑均已安装 Tailscale,并登录同一个账号。
- 手机上安装 Termius。
- Windows 在远程使用期间保持开机、联网,且不要进入会导致网络中断的深度睡眠状态。
先在 Windows PowerShell 中查看准确的 WSL 发行版名称:
powershell
wsl --list --verbose
我的发行版名称是:
text
Ubuntu-26.04_20260603
因此进入它的命令是:
powershell
wsl -d Ubuntu-26.04_20260603
如果只写 wsl -d Ubuntu,但系统中不存在这个名称,就会出现:
text
WSL_E_DISTRO_NOT_FOUND
三、在 Windows 上启用 OpenSSH Server
以管理员身份打开 PowerShell,依次执行:
powershell
# 查询当前系统中已安装或可用的 OpenSSH Server 功能组件状态
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
# 在线安装 OpenSSH Server 功能(版本 0.0.1.0)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# 立即启动 sshd 服务,使 SSH 服务器开始运行
Start-Service sshd
# 将 sshd 服务设置为开机自动启动,确保重启后 SSH 服务可用
Set-Service -Name sshd -StartupType Automatic
# 查看 sshd 服务的当前运行状态,确认服务是否正常运行
Get-Service sshd
最后一条命令应显示:
text
Status Name DisplayName
------ ---- -----------
Running sshd OpenSSH SSH Server
再检查 22 端口是否监听:
powershell
netstat -an | findstr :22
如果看到类似下面的内容,说明 SSH 服务已经在监听:
text
TCP 0.0.0.0:22 0.0.0.0:0 LISTENING
TCP [::]:22 [::]:0 LISTENING

检查 Windows 防火墙规则:
powershell
Get-NetFirewallRule -Name OpenSSH-Server-In-TCP
应能看到规则已启用、方向为入站、动作为允许。

如果该规则不存在,可以用管理员 PowerShell 创建:
powershell
New-NetFirewallRule `
-Name OpenSSH-Server-In-TCP `
-DisplayName 'OpenSSH SSH Server (sshd)' `
-Enabled True `
-Direction Inbound `
-Protocol TCP `
-Action Allow `
-LocalPort 22
四、确认 Tailscale 网络
在电脑和手机上打开 Tailscale,确认:
- 两台设备都处于在线状态;
- 登录的是同一个 Tailnet;
- Windows 端允许传入连接;
- 手机能够看到电脑的 Tailscale IP,通常形如
100.x.x.x。
接下来 Termius(手机APP) 中填写的是 Windows 的 Tailscale IP,不是:
localhost;- 家庭路由器分配的局域网地址;
- WSL2 中类似
172.x.x.x的虚拟网卡地址。
这套做法不需要在公网路由器上转发 22 端口。
五、在 Termius 中添加 Windows 主机
在 Termius 中新建 Host:
text
Address:Windows 的 Tailscale IP
Port:22
Username:Windows 用户名,例如 User
Password:Windows 账户密码
这里容易混淆的是 Windows Hello PIN。OpenSSH 通常验证的是 Windows 账户密码,不是开机 PIN;如果两者恰好设置成相同字符串,看起来会像是使用 PIN 登录。
首次连接时,Termius 会显示服务器主机密钥指纹。确认连接目标确实是自己的电脑后,再接受并加入 Known Hosts。
连接成功后看到的是 Windows 命令行,而不是 Ubuntu:

随后输入:
powershell
wsl -d Ubuntu-26.04_20260603
此时才真正进入 WSL2 Ubuntu。
六、用 tmux 承载 Codex CLI
如果 WSL 中尚未安装 tmux:
bash
sudo apt update
sudo apt install -y tmux
新建一个名为 codex 的会话:
bash
tmux new -s codex
底部出现类似下面的状态栏,说明已经进入 tmux:
text
[codex] 0:bash*

进入项目目录:
bash
cd ~/projects/automated-game-guide-site-poc
如果项目使用 Python 虚拟环境,可以激活它;这一步对 Codex CLI 本身不是必需的,但能让 Codex 调用项目中的 Python 工具:
bash
source .venv/bin/activate
启动新的 Codex 会话:
bash
codex
恢复最近一次会话:
bash
codex resume --last
也可以恢复指定的会话 ID:
bash
codex resume <session-id>
OpenAI 官方文档将 codex resume 定义为重新打开当前仓库中的近期本地聊天,或者从已保存的本地聊天中选择要继续的会话。参见 Codex CLI 官方文档。
在手机 Termius 中恢复成功后的效果如下:

七、正确离开和重新进入 tmux
这是整套方案最关键的操作。
暂时离开,但让 Codex 继续运行
依次按:
text
Ctrl+B
松开所有按键
再按小写 d
成功后会看到:
text
[detached from codex]
不要使用 Ctrl+C。它通常会中断当前任务或退出 Codex,而不是让 tmux 在后台保留界面。
重新进入已有会话
bash
tmux attach -t codex
如果要把会话从另一个终端强制转移到当前终端:
bash
tmux attach -d -t codex
查看已有 tmux 会话:
bash
tmux ls
如果执行 tmux new -s codex 时出现:
text
duplicate session: codex
这不是故障,而是说明会话已经存在。此时应该运行 tmux attach -t codex,而不是重复创建。
只有确定不再需要会话及其中的进程时,才删除它:
bash
tmux kill-session -t codex
八、解决 already has an active writer
我在迁移旧 Codex 会话到 tmux 时遇到过这个错误:
text
thread/resume failed:
thread ... already has an active writer

它表示该 Codex 会话仍被另一个 Codex 进程占用,不能在第二个终端中同时作为写入者恢复。需要先回到旧终端正常退出 Codex。
如果旧终端已经无法访问,可以先检查进程:
bash
pgrep -af codex
输出中可能同时包含:
codex:实际的 Codex CLI 进程;codex-clipboard-bridge:剪贴板桥接服务;tmux new -s codex:命令参数中碰巧包含codex的 tmux 进程。
不要看到 codex 字样就全部结束。确认某一行的命令确实是独立的 codex 进程后,先发送正常终止信号:
bash
kill <codex-pid>
等待片刻并再次确认:
bash
pgrep -af codex

确认旧 Codex 进程消失后,再运行:
bash
codex resume --last

只有普通 kill 无法结束目标进程时,才把 kill -9 <pid> 作为最后手段;强制结束可能让程序没有机会执行正常清理。
九、解决手机打字和粘贴困难
手机终端最大的痛点不是连接,而是输入。实际使用中可以采用三种方法。
1. 使用 Termius Paste mode
先在 ChatGPT、备忘录或其他应用中写好命令或提示词并复制,然后:
- 回到 Termius 并打开手机键盘;
- 在快捷栏上方把输入方式切换到 Paste mode;
- 从 Gboard 等输入法的剪贴板中插入内容;
- 检查完整文本后一次性发送。
Paste mode 也很适合语音输入:切换后点击手机键盘的麦克风,把完整提示词说完再提交。
2. 把常用命令保存成 Snippets
Termius 底部快捷栏的 {} 按钮可以打开 Snippets。可以保存一个"进入 Codex"的命令:
powershell
wsl.exe -d Ubuntu-26.04_20260603 -e tmux new-session -A -s codex -c /home/user/projects/automated-game-guide-site-poc
其中:
wsl.exe -d ...进入指定 Ubuntu;tmux new-session -A表示会话存在就附加,不存在就创建;-s codex指定会话名称;-c ...指定新建会话时的项目目录。
再保存一个"恢复 Codex"的 Snippet:
bash
codex resume --last
以后连接 Windows 后,点两次 Snippet 基本就能回到工作状态。
3. 配置 Termius Startup Command
如果当前 Termius 版本和套餐支持 Startup Command,可以把上面的 wsl.exe ... tmux new-session ... 设置为该 Host 的启动命令。连接 SSH 后就会自动进入 WSL 和 tmux。
Termius 官方也推荐移动端 AI 工作流使用 tmux、Paste mode、语音输入、可自定义快捷栏以及 Snippets。参见 8 tips for using AI agents on mobile with Termius。
十、常见问题速查
| 现象 | 常见原因 | 处理方法 |
|---|---|---|
Connection refused |
sshd 未运行或 22 端口未监听 |
Start-Service sshd,然后检查 `netstat -an |
| Termius 一直超时 | Tailscale 未连通、Windows 休眠或入站连接被阻止 | 检查两端 Tailscale 状态、电源设置和防火墙 |
| 密码始终错误 | 输入了 Windows Hello PIN,而不是账户密码 | 使用 Windows 账户密码;稳定后改用 SSH 密钥 |
WSL_E_DISTRO_NOT_FOUND |
发行版名称写错 | 用 wsl --list --verbose 获取准确名称 |
duplicate session: codex |
tmux 会话已经存在 | 使用 tmux attach -t codex |
already has an active writer |
旧 Codex 进程仍占用会话 | 正常退出旧 Codex,或定位并结束对应 PID |
| 手机锁屏后 SSH 断开 | 移动系统限制后台运行 | tmux 可保住服务端会话;Android 还可关闭 Termius 的电池优化 |
| 中文显示为方框或乱码 | 终端字体或 UTF-8 locale 不匹配 | 检查 Termius 字体、终端编码及 WSL 的 locale 设置 |
十一、最终的日常使用流程
电脑端第一次启动:
bash
wsl -d Ubuntu-26.04_20260603
tmux new -s codex
cd ~/projects/automated-game-guide-site-poc
source .venv/bin/activate
codex resume --last
准备离开电脑时:
text
Ctrl+B,然后按 d
手机端:
- 打开 Tailscale;
- 用 Termius 连接 Windows 的 Tailscale IP;
- 运行保存好的"进入 Codex"Snippet;
- 继续操作同一个 Codex CLI 会话。
回到电脑并希望把会话从手机转回来:
bash
tmux attach -d -t codex
总结
这套方案解决了三个问题:
- 去掉 ChatGPT Desktop 中转:手机直接通过 SSH 控制本地开发环境,响应更直接,也不会因为中转对话额外消耗 token。
- 避免暴露公网端口:Tailscale 提供设备间的私有网络连接。
- 避免断线丢会话:Codex CLI 运行在 tmux 中,手机和电脑可以在同一个终端状态之间切换。
最终体验接近"把电脑上的 Codex CLI 装进手机":手机只负责显示和输入,代码、依赖、密钥和算力仍留在自己的 WSL2 环境中。