windows-host-browser
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWindows host browser
Windows主机浏览器
The target is a long-lived Chrome on Iliyas's Windows PC, started with
. It is not a
headless sandbox: it is a real browser Iliyas also uses, with his live
sessions (Vercel, Google Cloud, and more), rendered on his physical screen.
--remote-debugging-port=<PORT> --user-data-dir="C:\chrome-debug"Two consequences drive everything below:
- Power — anything behind his logins is reachable without asking for credentials, and a page can be debugged exactly as he sees it: open tabs, run JS in them, watch network traffic, route through a chosen proxy.
- Restraint — he sees every window you open and shares every setting you change. Use this browser only on his explicit request; when he names the browser in the request, that settles it. Tell him when you open something visible, close tabs you opened unless he wants them kept, and undo browser-wide changes (proxy!) when done. For data you could equally get by asking him or via an authed CLI, prefer that.
目标是Iliyas的Windows电脑上长期运行的Chrome浏览器,启动命令为。它并非无头沙箱环境:这是Iliyas日常使用的真实浏览器,包含他的活跃会话(Vercel、Google Cloud等),会在他的物理屏幕上渲染内容。
--remote-debugging-port=<PORT> --user-data-dir="C:\chrome-debug"以下两点决定了后续所有操作的规则:
- 权限 —— 所有需登录才能访问的内容都可直接获取,无需索要凭据;页面可完全按照他看到的样子进行调试:打开标签页、在其中运行JS、监控网络流量、通过指定代理路由请求。
- 约束 —— 你打开的每个窗口、更改的每个设置都会被他看到。仅在他明确要求时才可使用此浏览器;当他在请求中指定该浏览器时,才可执行操作。打开可见内容时需告知他,关闭你打开的标签页(除非他要求保留),操作完成后恢复浏览器全局设置(如代理!)。对于可通过询问他或经认证的CLI获取的数据,优先选择这些方式。
Step 0 — connect: run connect.sh
, use the fixed endpoint
connect.sh步骤0 —— 连接:运行connect.sh
,使用固定端点
connect.shOn dev-remote the endpoint is fixed: , no matter
which port Chrome uses on the Windows side. One command sets everything up:
http://127.0.0.1:18800bash
~/.agents/skills/windows-host-browser/connect.sh在dev-remote上,端点是固定的:,无论Windows端Chrome使用哪个端口。只需一条命令即可完成所有设置:
http://127.0.0.1:18800bash
~/.agents/skills/windows-host-browser/connect.shon success prints: CDP_HTTP=http://127.0.0.1:18800
成功时会输出: CDP_HTTP=http://127.0.0.1:18800
export CDP_HTTP="http://127.0.0.1:18800"
The script is idempotent and cheap when everything is already up. When it
isn't, it rediscovers the current Windows-side port from the `chromedebug`
scheduled task (the source of truth — the port moves, see "Port gotcha";
**never hardcode it**), relaunches Chrome via the task if the process is dead
(a visible window on Iliyas's screen — say so), and rebuilds both SSH hops
(dev-remote → wsl → windows) onto local port 18800.
A systemd timer on dev-remote re-runs it every 2 minutes, so the endpoint is
normally already alive: `systemctl status cdp-tunnel.timer`, logs in
`journalctl -u cdp-tunnel.service`.
If `connect.sh` prints `FAIL`, its message names the layer that broke; the
sections below are the manual troubleshooting path. As of 2026-09-01 the
Windows-side port is **9555** (9222, 9223, 9250, 9333 and 9444 got
WinNAT-blocked in turn).export CDP_HTTP="http://127.0.0.1:18800"
这个脚本是幂等的,当所有连接已建立时执行成本很低。若未建立连接,它会从`chromedebug`计划任务中重新发现Windows端当前使用的端口(这是权威来源——端口会变动,详见“端口陷阱”;**切勿硬编码端口**);若Chrome进程已终止,则通过该任务重新启动Chrome(会在Iliyas的屏幕上显示一个可见窗口——需告知他);并重建两条SSH隧道(dev-remote → wsl → windows),映射到本地端口18800。
dev-remote上的systemd定时器每2分钟重新运行一次该脚本,因此端点通常已处于活跃状态:可通过`systemctl status cdp-tunnel.timer`查看状态,日志位于`journalctl -u cdp-tunnel.service`。
如果`connect.sh`输出`FAIL`,其消息会指出故障所在的层级;以下章节是手动排查步骤。截至2026年9月1日,Windows端端口为**9555**(9222、9223、9250、9333和9444依次被WinNAT阻止)。Manual path (what connect.sh automates)
手动流程(connect.sh自动化的操作)
The debug port lives only on the Windows host's own loopback. The home
machine (the WSL box ) and are both on Iliyas's
Tailscale tailnet, the stable path between them. Reaching the port is a
two-hop tunnel — the WSL box cannot see the Windows loopback directly, so it
must tunnel to the host too. Discover the port first:
iliyasonedev-remotebash
PORT=$(ssh wsl 'ssh windows "schtasks /query /tn chromedebug /xml"' \
| grep -aoE 'remote-debugging-port=[0-9]+' | grep -oE '[0-9]+')-
On the WSL box: bring the host port onto WSL's loopback:
iliyasonebashpgrep -f "ssh .*-L $PORT:127.0.0.1:$PORT windows" >/dev/null \ || ssh -f -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \ -L "$PORT:127.0.0.1:$PORT" windows(also works without the tunnel, for one-off checks.)ssh windows "curl 127.0.0.1:$PORT/json/version" -
On:
dev-remotereaches the WSL shell over the tailnet (ssh wsl). Chain a second forward on top of the WSL→host one above:100.93.231.101bashpgrep -f "ssh -N .*-L $PORT:127.0.0.1:$PORT wsl" >/dev/null \ || setsid ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \ -L "$PORT:127.0.0.1:$PORT" wsl >/dev/null 2>&1 < /dev/null &
Then run the check:
bash
curl -s --max-time 4 "http://127.0.0.1:$PORT/json/version" || echo NO_CDPA Chrome version → go to Step 1. → causes, cheapest first: a tunnel
isn't up (re-run the forwards above); the port was stolen by WinNAT even though
Chrome is running (see "Port gotcha"); the home PC / WSL box is offline
( — if is offline, the machine is off, nothing to
fix from here); or the debug Chrome isn't running (see "Launching"). Do not
fabricate a path.
NO_CDPtailscale statusiliyasone调试端口仅存在于Windows主机自身的回环接口上。家用机器(WSL主机)和都位于Iliyas的Tailscale网络中,这是二者之间的稳定连接路径。要访问该端口需建立两层隧道——WSL主机无法直接访问Windows回环接口,因此也需建立到Windows主机的隧道。首先需发现端口:
iliyasonedev-remotebash
PORT=$(ssh wsl 'ssh windows "schtasks /query /tn chromedebug /xml"' \
| grep -aoE 'remote-debugging-port=[0-9]+' | grep -oE '[0-9]+')-
在WSL主机上:将Windows主机端口映射到WSL的回环接口:
iliyasonebashpgrep -f "ssh .*-L $PORT:127.0.0.1:$PORT windows" >/dev/null \ || ssh -f -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \ -L "$PORT:127.0.0.1:$PORT" windows(无需隧道也可工作,适用于一次性检查。)ssh windows "curl 127.0.0.1:$PORT/json/version" -
在上:
dev-remote通过Tailscale网络(ssh wsl)连接到WSL shell。在上述WSL→主机的隧道基础上,再建立一层转发:100.93.231.101bashpgrep -f "ssh -N .*-L $PORT:127.0.0.1:$PORT wsl" >/dev/null \ || setsid ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \ -L "$PORT:127.0.0.1:$PORT" wsl >/dev/null 2>&1 < /dev/null &
然后运行检查:
bash
curl -s --max-time 4 "http://127.0.0.1:$PORT/json/version" || echo NO_CDP返回Chrome版本信息 → 进入步骤1。返回 → 原因(从易到难):隧道未建立(重新运行上述转发命令);端口被WinNAT占用但Chrome仍在运行(详见“端口陷阱”);家用PC/WSL主机离线(——若离线,则机器已关机,无法从dev-remote修复);或调试Chrome未运行(详见“启动浏览器”)。请勿自行编造解决方案。
NO_CDPtailscale statusiliyasoneStep 1 — connect
步骤1 —— 建立连接
One gotcha applies to every websocket connection, not just the helper: Chrome
was started without , so open CDP websockets with
no Origin header or the connection is rejected.
--remote-allow-originsFrom there it is plain CDP against :
$CDP_HTTP- — tabs and their websocket URLs; attach with any CDP client to evaluate JS, capture screenshots, or watch network events (
GET /json/list+Network.enable/Network.requestWillBeSent).responseReceived - — open a page (a visible tab on Iliyas's screen — say so when you do it).
PUT /json/new?url=…
每个WebSocket连接都存在一个陷阱:Chrome启动时未添加参数,因此打开CDP WebSocket时不要携带Origin头,否则连接会被拒绝。
--remote-allow-origins接下来,只需针对执行标准CDP操作即可:
$CDP_HTTP- —— 获取标签页及其WebSocket URL;可通过任意CDP客户端附加到标签页,执行JS、捕获截图或监控网络事件(
GET /json/list+Network.enable/Network.requestWillBeSent)。responseReceived - —— 打开页面(会在Iliyas的屏幕上显示一个可见标签页——操作时需告知他)。
PUT /json/new?url=…
Reuse existing windows — don't multiply them
复用现有窗口——不要新增窗口
Every window and tab is on Iliyas's screen, and he has asked agents to stop
spawning new windows. Before opening anything, and reuse
what's there, in this order:
GET /json/list- A blank tab exists (or
about:blank): navigate it (attach to its websocket,chrome://newtab/) instead of creating a target.Page.navigate - The open tabs are your own or clearly idle (not something Iliyas is
actively working in): open your page as a tab in that same window —
does this, it targets an existing window.
PUT /json/new - A new window only when you deliberately want one — e.g. the existing window is full of Iliyas's unrelated active work, or you need isolation (different window size, a flow he should watch separately). Say why.
Same on cleanup: the tabs you opened; never close tabs
you didn't open.
/json/close/<id>每个窗口和标签页都会显示在Iliyas的屏幕上,他已要求代理停止生成新窗口。打开任何内容之前,先调用并按以下顺序复用现有资源:
GET /json/list- 存在空白标签页(或
about:blank):导航该标签页(附加到其WebSocket,调用chrome://newtab/),而非创建新目标。Page.navigate - 已打开的标签页属于你或明显处于空闲状态(不是Iliyas正在处理的内容):在同一窗口中打开你的页面——会执行此操作,它会针对现有窗口。
PUT /json/new - 仅在刻意需要时才打开新窗口——例如,现有窗口中充满了Iliyas无关的活跃工作内容,或者你需要隔离环境(不同窗口尺寸、他需要单独查看的流程)。需说明原因。
清理时同理:调用关闭你打开的标签页;切勿关闭你未打开的标签页。
/json/close/<id>Proxy control — cdp.py
(per profile)
cdp.py代理控制 —— cdp.py
(按配置文件)
cdp.pyChrome carries the Proxy Switcher extension
(), which owns the proxy setting. The proxy
is per Chrome profile, not per window: the browser can run several
profiles at once (separate windows, cookies, extension copies), and each can
sit behind a different proxy. (next to this file) drives it. It
defaults to the fixed endpoint (run
first); overrides that only when running somewhere else.
iejkjpdckomcjdhmkemlfdapjodcpgihcdp.pyhttp://127.0.0.1:18800connect.sh$CDP_HTTPbash
python3 cdp.py profiles # list running profiles by their open tabs
python3 cdp.py get -p 2 # proxy setting of profile 2 from that list
python3 cdp.py set 82.38.65.142 41196 http proxyuser 'PASSWORD' -p proton
python3 cdp.py egress -p proton # prove the exit IP/country through it
python3 cdp.py direct -p proton # revert that profile to direct--profile-pprofilesTwo gotchas the tool hides: the extension is MV3, so its per-profile service
worker sleeps and drops out of the target list — and a sleeping worker in a
specific profile can't be woken via (CDP refuses real
profiles' browserContextIds). wakes it by ing a
throwaway tab from one of that profile's own pages, CDP-navigating it to the
extension popup, then closing it.
Target.createTargetcdp.pywindow.opensetchrome.proxy.settings.setauth-usernameauth-passwordonAuthRequiredAlways when done on the profile Iliyas browses in himself — a
proxy left there changes his own browsing. A dedicated proxy profile can keep
its proxy.
directAdding the extension to a new profile is a manual step: open
in that profile's window and have Iliyas click "Add to Chrome" — the install
confirmation is native UI, unreachable over CDP.
https://chromewebstore.google.com/detail/iejkjpdckomcjdhmkemlfdapjodcpgihChrome安装了Proxy Switcher扩展(),该扩展负责代理设置。代理是按Chrome配置文件设置的,而非按窗口:浏览器可同时运行多个配置文件(独立窗口、Cookie、扩展副本),每个配置文件可使用不同的代理。(与本文档同目录)用于控制该扩展。它默认使用固定端点(需先运行);仅在其他环境运行时,才会覆盖该端点。
iejkjpdckomcjdhmkemlfdapjodcpgihcdp.pyhttp://127.0.0.1:18800connect.sh$CDP_HTTPbash
python3 cdp.py profiles # 通过打开的标签页列出运行中的配置文件
python3 cdp.py get -p 2 # 获取该列表中配置文件2的代理设置
python3 cdp.py set 82.38.65.142 41196 http proxyuser 'PASSWORD' -p proton
python3 cdp.py egress -p proton # 验证该配置文件的出口IP/地区
python3 cdp.py direct -p proton # 将该配置文件恢复为直接连接--profile-pprofiles工具隐藏了两个陷阱:该扩展是MV3版本,因此其按配置文件的服务工作线程会休眠并从目标列表中消失——且特定配置文件中休眠的工作线程无法通过唤醒(CDP拒绝真实配置文件的browserContextIds)。通过从该配置文件的某个页面一个临时标签页,通过CDP导航到扩展弹窗,然后关闭该标签页来唤醒工作线程。
Target.createTargetcdp.pywindow.opensetchrome.proxy.settings.setauth-usernameauth-passwordonAuthRequired操作完成后务必将Iliyas日常使用的配置文件恢复为直接连接——残留的代理设置会影响他自己的浏览。专用代理配置文件可保留其代理设置。
将该扩展添加到新配置文件需手动操作:在该配置文件的窗口中打开,并让Iliyas点击“添加至Chrome”——安装确认是原生UI,无法通过CDP访问。
https://chromewebstore.google.com/detail/iejkjpdckomcjdhmkemlfdapjodcpgihWhere proxies come from
代理来源
The reverse-api project owns a table (Postgres, Heroku app
): columns .
HTTP proxies with user/pass auth, selected by (ISO-3166
alpha-2). Query it rather than hardcoding credentials:
proxypinc000scheme, server, port, username, password, country_codecountry_codebash
undefinedreverse-api项目维护着一个表(Postgres,Heroku应用):列包括。带用户名/密码认证的HTTP代理,可按(ISO-3166 alpha-2)筛选。请查询该表,而非硬编码凭据:
proxypinc000scheme, server, port, username, password, country_codecountry_codebash
undefinedon dev-remote, heroku CLI is authed to app pinc000
在dev-remote上,Heroku CLI已认证到应用pinc000
heroku pg:psql -a pinc000 -c
"select scheme,server,port,username,password,country_code from proxy where country_code='nl';"
"select scheme,server,port,username,password,country_code from proxy where country_code='nl';"
There is no rotation — a proxy is sticky per account — so for browser use
pick any row for the country you want.heroku pg:psql -a pinc000 -c
"select scheme,server,port,username,password,country_code from proxy where country_code='nl';"
"select scheme,server,port,username,password,country_code from proxy where country_code='nl';"
代理无需轮换——每个账户对应固定代理——因此浏览器使用时只需选择目标国家的任意一行记录即可。Launching the debug Chrome
启动调试Chrome
If Step 0 says but works, start Chrome via the scheduled
task — never over plain SSH, which lands Chrome in the invisible session 0
where it exits without ever binding the port:
NO_CDPssh wslbash
undefined如果步骤0显示但可正常工作,则通过计划任务启动Chrome——切勿通过普通SSH启动,否则Chrome会进入不可见的会话0,无法绑定端口就会退出:
NO_CDPssh wslbash
undefinedfrom the WSL box:
从WSL主机执行:
ssh windows 'schtasks /run /tn chromedebug'
ssh windows 'schtasks /run /tn chromedebug'
from dev-remote (hop through WSL):
从dev-remote执行(通过WSL跳转):
ssh wsl 'ssh windows "schtasks /run /tn chromedebug"'
ssh wsl 'ssh windows "schtasks /run /tn chromedebug"'
then poll: curl -s "$CDP_HTTP/json/version"
然后轮询:curl -s "$CDP_HTTP/json/version"
This opens a visible window on Iliyas's screen — say so when you do it. The
task runs as `LogonType=InteractiveToken` (General tab: "Run only when user is
logged on"), so it launches into his visible session and stores no password.
If the relaunch works but Chrome is dead again minutes later, don't keep
relaunching — see "Death gotcha".
这会在Iliyas的屏幕上打开一个可见窗口——操作时需告知他。该任务的`LogonType=InteractiveToken`(常规选项卡:“仅当用户登录时运行”),因此会启动到他的可见会话中,且不存储密码。
如果重新启动成功但Chrome几分钟后再次终止,则不要持续重启——详见“进程终止陷阱”。Iliyas can open it himself — the desktop shortcut
Iliyas可手动打开——桌面快捷方式
"Agent Chrome" on the Windows desktop opens this browser by hand — e.g.
to sign in to a service so agents can then use the session. It runs
: if the debug Chrome is already
running it opens a new window in it; otherwise it starts the
task (a plain would be silently ignored while the task
instance is still running, because the task uses — that's why the script checks first). So when he needs to log in
somewhere, point him at the shortcut instead of opening tabs for him.
C:\chrome-debug\open-debug-chrome.ps1chromedebugschtasks /runMultipleInstances IgnoreNewIf the shortcut or opener script is missing, recreate both by running
(next to this file) on the host via the
transport. It builds the in and moves it to the
desktop — fails to save directly into the desktop folder
because its name is Cyrillic ().
mk-shortcut.ps1-EncodedCommand.lnkC:\chrome-debugWScript.ShellРабочий столWindows桌面上的**"Agent Chrome"**快捷方式可手动打开此浏览器——例如,登录某个服务以便代理后续使用该会话。它会运行:如果调试Chrome已在运行,则在其中打开一个新窗口;否则启动任务(若任务实例仍在运行,直接执行会被静默忽略,因为任务设置为——这就是脚本先检查状态的原因)。因此当他需要登录某个服务时,让他点击该快捷方式,而非为他打开标签页。
C:\chrome-debug\open-debug-chrome.ps1chromedebugschtasks /runMultipleInstances IgnoreNew如果快捷方式或启动脚本丢失,可通过传输方式在主机上运行(与本文档同目录)重新创建。它会在中生成文件,然后移动到桌面——无法直接保存到桌面文件夹,因为桌面名称是西里尔文()。
-EncodedCommandmk-shortcut.ps1C:\chrome-debug.lnkWScript.ShellРабочий столPort gotcha — WinNAT can steal the debug port
端口陷阱——WinNAT可能抢占调试端口
Symptom: the debug Chrome is running and browses fine, but
refuses the connection and no DevTools server exists.
Launched with ,
shows then
.
$CDP_HTTP/json/version--enable-logging --v=1C:\chrome-debug\chrome_debug.logbind() ... Only one usage of each socket address ... (0x2740)Cannot start http server for devtoolsCause: Hyper-V/WSL2 WinNAT/HNS reserves blocks of TCP ports; after a
WSL or host restart a block can include the debug port. It becomes unbindable
even though shows nothing on it AND it is absent from
. "Worked yesterday, broken today" =
the reserved block moved onto the port. Custom user-data-dir, policies, and
session 0 are red herrings here — confirm with the log line above.
netstatnetsh int ipv4 show excludedportrangeRecovery (no admin, no service restart):
-
Find a free port — try to bind candidates and pick the first that succeeds:powershell
foreach ($p in 9223,9250,9333,9555,18222) { try { $l=[System.Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,$p) $l.Start(); "OK $p"; $l.Stop() } catch { "FAIL $p" } } -
Repoint the task to that port. Use PowerShell, not
Set-ScheduledTask— the latter wrongly prompts for a Windows password even though the task stores none:schtasks /changepowershell$a = New-ScheduledTaskAction -Execute 'C:\Program Files\Google\Chrome\Application\chrome.exe' ` -Argument '--remote-debugging-port=<PORT> --user-data-dir=C:\chrome-debug' Set-ScheduledTask -TaskName 'chromedebug' -Action $aQuoting throughis brittle; run PowerShell viassh wsl 'ssh windows "..."'to avoid it.-EncodedCommand <base64-UTF16LE> -
Kill the old instances, relaunch, verify:powershell
Get-CimInstance Win32_Process -Filter "name='chrome.exe'" | ? { $_.CommandLine -like '*chrome-debug*' } | % { Stop-Process $_.ProcessId -Force } schtasks /run /tn chromedebug (Invoke-WebRequest -UseBasicParsing http://127.0.0.1:<PORT>/json/version).Content -
Re-run the Step 0 tunnels with the new.
$PORT
Durable fix (optional, needs admin): reserve the port so WinNAT won't grab
it — . Stopping
winnat briefly drops WSL2/Hyper-V NAT, which can blip an SSH path that runs
through it — do it only when a short interruption is safe.
net stop winnat; netsh int ipv4 add excludedportrange protocol=tcp startport=<PORT> numberofports=1 store=persistent; net start winnat症状:调试Chrome正在运行且可正常浏览,但拒绝连接,且不存在DevTools服务器。启动时添加参数,会显示,然后显示。
$CDP_HTTP/json/version--enable-logging --v=1C:\chrome-debug\chrome_debug.logbind() ... Only one usage of each socket address ... (0x2740)Cannot start http server for devtools原因:Hyper-V/WSL2的WinNAT/HNS会保留TCP端口块;WSL或主机重启后,某个端口块可能包含调试端口。即使显示该端口无占用,且中也不存在该端口,它仍无法被绑定。“昨天正常,今天故障”意味着保留的端口块覆盖了该调试端口。自定义用户数据目录、策略和会话0都是无关因素——以上述日志行作为判断依据。
netstatnetsh int ipv4 show excludedportrange恢复方法(无需管理员权限,无需重启服务):
-
找到可用端口——尝试绑定候选端口,选择第一个成功的:powershell
foreach ($p in 9223,9250,9333,9555,18222) { try { $l=[System.Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,$p) $l.Start(); "OK $p"; $l.Stop() } catch { "FAIL $p" } } -
将计划任务指向该新端口。使用PowerShell的,不要使用
Set-ScheduledTask——后者会错误地提示输入Windows密码,尽管任务并未存储密码:schtasks /changepowershell$a = New-ScheduledTaskAction -Execute 'C:\Program Files\Google\Chrome\Application\chrome.exe' ` -Argument '--remote-debugging-port=<PORT> --user-data-dir=C:\chrome-debug' Set-ScheduledTask -TaskName 'chromedebug' -Action $a通过传递引号参数容易出错;建议通过ssh wsl 'ssh windows "..."运行PowerShell以避免此问题。-EncodedCommand <base64-UTF16LE> -
终止旧实例,重新启动并验证:powershell
Get-CimInstance Win32_Process -Filter "name='chrome.exe'" | ? { $_.CommandLine -like '*chrome-debug*' } | % { Stop-Process $_.ProcessId -Force } schtasks /run /tn chromedebug (Invoke-WebRequest -UseBasicParsing http://127.0.0.1:<PORT>/json/version).Content -
使用新的重新运行步骤0的隧道。
$PORT
持久修复(可选,需管理员权限):保留该端口,避免WinNAT抢占——执行。停止winnat会短暂中断WSL2/Hyper-V NAT,可能导致通过该路径的SSH连接短暂中断——仅在允许短时间中断时执行此操作。
net stop winnat; netsh int ipv4 add excludedportrange protocol=tcp startport=<PORT> numberofports=1 store=persistent; net start winnatDeath gotcha — Task Scheduler kills Chrome on battery flap
进程终止陷阱——计划任务在电源切换时终止Chrome
Symptom: the opposite of the Port gotcha — the debug Chrome process
dies minutes after every launch (0 with in the
command line), while the debug port is free and bindable. brings CDP back, then it's dead again within ~2 minutes.
chrome.exechrome-debugschtasks /run /tn chromedebugCause: the PC is a laptop, and a task created with default settings gets
/ . Windows'
AC/battery status can flap every couple of minutes even with the charger
plugged in (battery-care charge limiting, loose connector), and on every
flap Task Scheduler terminates the task's Chrome. The proof is Task Scheduler
operational-log event 327: The
task already carries the fixed settings, but any recreation of
the task with / default settings
silently reintroduces the killers — so re-check the settings whenever this
symptom returns.
StopIfGoingOnBatteries=trueDisallowStartIfOnBatteries=trueTask Scheduler stopped instance ... of task "\chromedebug" because the computer is switching to battery power.chromedebugNew-ScheduledTaskRegister-ScheduledTaskDiagnose:
-
— in
schtasks /query /tn chromedebug /xml,<Settings>/StopIfGoingOnBatteriesmust beDisallowStartIfOnBatteriesandfalseExecutionTimeLimit(unlimited). If they aren't, that's the bug — go straight to the fix.PT0S -
The Task Scheduler operational log is disabled by default; enable it and catch the next death in the act:powershell
wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true # after a death: Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 100 | ? { $_.Message -match 'chromedebug' } | select TimeCreated, Id, MessageEvent 327 with "switching to battery power" = this gotcha. No 32x stop-event at all = Chrome itself crashed — addto the task action, reproduce, read--enable-logging --v=1, then revert the flags.C:\chrome-debug\chrome_debug.log -
Battery reality check:(a result means it's a laptop) and
Get-CimInstance Win32_Battery(after[System.Windows.Forms.SystemInformation]::PowerStatus) for the current AC state.Add-Type -AssemblyName System.Windows.Forms
Durable fix — replace the task's settings, keeping the action and the
principal ( never touches parts you
don't pass). Put this in a and run it through the
transport rather than quoting it inline across the two SSH hops:
InteractiveTokenSet-ScheduledTask.ps1-EncodedCommandpowershell
$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
Set-ScheduledTask -TaskName 'chromedebug' -Settings $sbash
undefined症状:与端口陷阱相反——调试Chrome进程每次启动后几分钟就会终止(命令行中包含的进程数为0),但调试端口可用且可绑定。执行可恢复CDP连接,但几分钟后又会终止。
chrome-debugchrome.exeschtasks /run /tn chromedebug原因:该电脑是笔记本电脑,默认设置创建的任务会启用 / 。即使充电器已插入,Windows的AC/电池状态也可能每隔几分钟切换一次(电池养护充电限制、连接器松动),每次切换时计划任务都会终止Chrome。证据是计划任务操作日志中的事件327: 任务已配置为固定设置,但使用/默认设置重新创建任务时,会重新引入这些终止设置——因此出现此症状时需重新检查设置。
StopIfGoingOnBatteries=trueDisallowStartIfOnBatteries=trueTask Scheduler stopped instance ... of task "\chromedebug" because the computer is switching to battery power.chromedebugNew-ScheduledTaskRegister-ScheduledTask诊断方法:
-
—— 在
schtasks /query /tn chromedebug /xml中,<Settings>/StopIfGoingOnBatteries必须为DisallowStartIfOnBatteries,false必须为ExecutionTimeLimit(无限制)。若不是,则这就是故障原因——直接执行修复步骤。PT0S -
计划任务操作日志默认是禁用的;启用日志并捕获下次终止事件:powershell
wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true # 终止事件发生后: Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' -MaxEvents 100 | ? { $_.Message -match 'chromedebug' } | select TimeCreated, Id, Message事件327且包含“switching to battery power” = 此陷阱。没有32x终止事件 = Chrome自身崩溃——在任务操作中添加参数,重现故障,读取--enable-logging --v=1,然后恢复参数。C:\chrome-debug\chrome_debug.log -
电池状态检查:(有结果表示是笔记本电脑),以及
Get-CimInstance Win32_Battery(需先执行[System.Windows.Forms.SystemInformation]::PowerStatus)查看当前AC状态。Add-Type -AssemblyName System.Windows.Forms
持久修复——替换任务的设置,保留操作和主体(不会修改未传递的部分)。将以下内容保存为脚本,通过传输方式执行,避免跨两层SSH跳转时的引号问题:
InteractiveTokenSet-ScheduledTask.ps1-EncodedCommandpowershell
$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
Set-ScheduledTask -TaskName 'chromedebug' -Settings $sbash
undefinedfrom dev-remote: encode UTF-16LE+base64, run via the double hop
从dev-remote执行:编码为UTF-16LE+base64,通过双层跳转运行
B64=$(iconv -f UTF-8 -t UTF-16LE /tmp/fix.ps1 | base64 -w0)
ssh wsl "ssh windows 'powershell -NoProfile -EncodedCommand $B64'"
(`-ExecutionTimeLimit` zero matters: `New-ScheduledTaskSettingsSet` otherwise
defaults it to 72 h, which would kill Chrome three days in.) Then relaunch and
— the real test — confirm the process count is still non-zero **4–5 minutes
later**, past the flap interval, not just that CDP answered once.B64=$(iconv -f UTF-8 -t UTF-16LE /tmp/fix.ps1 | base64 -w0)
ssh wsl "ssh windows 'powershell -NoProfile -EncodedCommand $B64'"
(`-ExecutionTimeLimit`设为0很重要:否则`New-ScheduledTaskSettingsSet`默认会设置为72小时,三天后会终止Chrome。)然后重新启动,并进行真正的测试——确认4-5分钟后进程数仍不为零(超过电源切换间隔),而不仅仅是CDP一次性响应。How the access is wired (and repairing it)
访问链路的工作原理(及修复方法)
Both machines are nodes on Iliyas's Tailscale tailnet:
- =
iliyasone— the WSL box on the home PC. Tailscale runs inside WSL, not on Windows; the Windows host is reached through WSL via100.93.231.101(→ssh windows, key127.0.0.1:2222). The Windows loopback is not shared into WSL, so reaching the debug port from WSL needs the~/.ssh/id_wintunnel from Step 0.ssh -L ... windows - =
dev-remote. Its100.105.176.17has~/.ssh/config→Host wsl, and its root key is in the WSL box's100.93.231.101, soauthorized_keysworks over the tailnet.ssh wsl
Tailnet addresses survive home-IP changes, so nothing needs reconfiguring
when Iliyas's network moves. When fails (or a manual check says
):
connect.shNO_CDP- Far end offline (PC asleep / WSL not up): shows
tailscale statusoffline. Nothing to fix from dev-remote.iliyasone - Chrome not running but works: see "Launching".
ssh wsl - Port stolen by WinNAT: see "Port gotcha".
- Chrome dies again right after relaunch: see "Death gotcha".
- Tunnel not up: re-run (it rebuilds both hops).
connect.sh - MTU blackhole on the tailnet path (seen 2026-08-15): works but
pinghangs atssh wsl, or small CDP calls (expecting SSH2_MSG_KEX_ECDH_REPLY) work while big ones (/json/version) return nothing — large packets are being dropped between dev-remote and/json/list. Two-part fix: force a small classic KEX on every ssh to wsl (iliyasone— the default post-quantum sntrup761 KEX sends oversized packets), and lower the interface MTU on dev-remote:-o KexAlgorithms=curve25519-sha256@libssh.org,curve25519-sha256(default 1280 exceeds the real path MTU). The MTU setting does not survive a dev-remote reboot, butip link set dev tailscale0 mtu 1200re-applies it on every non-fast-path run; checkconnect.shif big transfers stall anyway. Tunnels opened before the MTU fix keep their broken MSS — restart them after changing the MTU.ip link show tailscale0
Only one home node is on the tailnet today (). If Iliyas later
works from a different machine, it joins as a separate node with its own
name/IP — repoint (or add ) at it. Do not invent
non-tailnet routes.
iliyasoneHost wslHost wsl-<name>两台机器都是Iliyas的Tailscale网络中的节点:
- =
iliyasone—— 家用PC上的WSL主机。Tailscale运行在WSL内部,而非Windows上;Windows主机通过WSL的100.93.231.101访问(→ssh windows,密钥为127.0.0.1:2222)。Windows回环接口未共享到WSL中,因此从WSL访问调试端口需要步骤0中的~/.ssh/id_win隧道。ssh -L ... windows - =
dev-remote。其100.105.176.17中设置了~/.ssh/config→Host wsl,且其根密钥已添加到WSL主机的100.93.231.101中,因此authorized_keys可通过Tailscale网络正常工作。ssh wsl
Tailscale网络地址不受家用IP变化影响,因此Iliyas更换网络时无需重新配置。当失败(或手动检查显示)时:
connect.shNO_CDP- 远端离线(PC休眠/WSL未启动):显示
tailscale status离线。无法从dev-remote修复。iliyasone - Chrome未运行但正常工作:详见“启动浏览器”。
ssh wsl - 端口被WinNAT抢占:详见“端口陷阱”。
- Chrome重启后立即终止:详见“进程终止陷阱”。
- 隧道未建立:重新运行(它会重建两层隧道)。
connect.sh - Tailscale链路MTU黑洞(2026年8月15日出现):正常但
ping卡在ssh wsl,或小CDP调用(expecting SSH2_MSG_KEX_ECDH_REPLY)正常但大调用(/json/version)无响应——dev-remote与/json/list之间的大包被丢弃。修复分为两部分:强制所有到wsl的ssh连接使用小型经典KEX算法(iliyasone——默认的后量子sntrup761 KEX会发送超大包);降低dev-remote上的接口MTU:-o KexAlgorithms=curve25519-sha256@libssh.org,curve25519-sha256(默认1280超过实际链路MTU)。MTU设置不会在dev-remote重启后保留,但ip link set dev tailscale0 mtu 1200会在每次非快速路径运行时重新应用;若大传输仍停滞,可检查connect.sh。MTU修复前建立的隧道仍会使用错误的MSS——更改MTU后需重启隧道。ip link show tailscale0
目前只有一个家用节点()在Tailscale网络中。若Iliyas后续更换机器工作,新机器会作为单独节点加入,拥有自己的名称/IP——将指向它(或添加)。请勿创建非Tailscale路由。
iliyasoneHost wslHost wsl-<name>