Environment
| item |
value |
| bsk CLI |
0.3.0 (bsk --version), ~/.local/bin/bsk.exe, installed with the official install.ps1 |
| bsk daemon / protocol |
0.3.0 / 1.3 |
| extension / browser |
0.3.0 / Microsoft Edge 153.0.0.0 — connected (bsk browsers lists it, session_count: 0) |
| OS |
Windows 11 Pro, build 26200, x64 |
| callers used to reproduce |
Windows PowerShell 5.1.26100.7705, Node.js v24.13.0 (child_process.spawn), WMI Win32_Process.Create + cmd.exe |
bsk update --check |
up_to_date (0.3.0 is the current release) |
Summary
With no daemon running, every bsk command that is not a daemon subcommand hangs forever and prints nothing — browsers, status, session start, all of them. The daemon is started in the background (.bsk/daemon.json is written, the daemon log shows daemon ready and then browser connected), and a command issued later from another shell returns in ~100 ms — but the first process never returns and can only be killed.
bsk daemon start invoked directly returns in 191 ms, exit 0, and leaves a healthy daemon.
- With
BSK_AUTO_START=0 the same cold command returns in 86 ms, exit 2, with the proper actionable error:
error: ensure daemon is running: automatic daemon startup is disabled (BSK_AUTO_START=0); run 'bsk daemon start' in the owning host environment with the same BSK_HOME, then retry
So the hang lives exactly in the automatic daemon startup path (ensure daemon is running) — the code that starts the daemon on demand and then waits for it.
Steps to reproduce
bsk daemon stop # make sure no daemon is running
bsk browsers # <-- hangs forever, no output
bsk daemon stop — afterwards Get-Process bsk is empty.
- Run any non-daemon command (
bsk browsers, bsk status, bsk session start --no-focus). It blocks with zero stdout and zero stderr; -vv also produces nothing, so it blocks before anything is logged to the caller.
- In another shell,
bsk status returns immediately and reports a healthy daemon with the browser connected — the daemon was started successfully, only the waiting client is stuck.
- The first process stays stuck indefinitely (observed 60 s+, see the table); killing the daemon is the only thing that releases it.
Evidence (all observed on the machine above)
| cold-daemon invocation |
caller stdio |
result |
bsk browsers from PowerShell 5.1 (& bsk ... 2>&1 | Out-String) |
pipe |
hang, 0 output, > 60 s |
bsk -vv browsers from PowerShell |
pipe |
hang, 0 bytes on stderr |
bsk browsers from Node spawn (stdio default pipe, and ['ignore','pipe','pipe']) |
pipes |
hang, 0 output, > 20 s |
bsk browsers from a WMI-created cmd.exe with > out.txt 2> err.txt (outside any job object) |
file redirection |
hang, both files 0 bytes, > 19 s |
bsk browsers via Start-Process -WindowStyle Hidden (own console) |
real console |
hang, > 20 s |
bsk browsers from Node with a minimal clean environment (only PATH/SystemRoot/TEMP/USERPROFILE/...) |
pipe |
hang, > 20 s |
bsk browsers with BSK_AUTO_START=0 |
pipe |
86 ms, exit 2, clear error |
bsk daemon start directly |
pipe |
191 ms, exit 0, daemon healthy afterwards |
bsk browsers with a warm daemon |
pipe |
108-180 ms, exit 0 |
Daemon log of one cold hang (these lines and then nothing more):
INFO daemon lock acquired (t0 + 0 ms)
INFO ws server listening 127.0.0.1:52800
INFO daemon ready pid=... ws_port=52800
INFO ipc named-pipe server listening \.\pipe\bsk-daemon-<hash>
INFO browser connected id=<browser-id> name=edge generation=1 (t0 + ~4 s)
Even after the extension is connected and the daemon is fully ready, the waiting client never proceeds.
Impact — it also freezes the caller
The auto-started daemon appears to inherit the caller stdio handles and keeps them open. For any consumer that spawns bsk <cmd> with piped stdio and waits for the stream (Node close event, child_process.execFileSync, Python subprocess.communicate()), the pipe never reaches EOF — so even after the consumer own timeout kills the CLI, the consumer stays blocked forever, an uncancellable hanging tool call.
Real incident in a downstream agent harness (DeepSeek Harness plugin @wxg-prc-cpg/browser-skill-dsh-plugin, which runs bsk <cmd> --json with piped stdio):
| time |
event |
| T+0 s |
harness spawns bsk session start --json; no daemon running |
| T+0 s |
daemon starts (log lines above); extension connects at T+4 s |
| T+120 s |
harness command timeout fires and cancels the CLI (the CLI process is gone afterwards), but the pipe stays open, so the harness keeps waiting |
| T+1168 s |
an operator kills the daemon; the pipes close and the harness unblocks in the same second |
This is the classic Windows wait-for-EOF-instead-of-exit trap: a descendant holds the write end of the pipe. The same pattern is documented for another daemon CLI in open-gsd/gsd-browser#62 (daemon start leaves the caller inherited stdio handles open, hanging any consumer that uses a pipe).
Hypothesized cause (outside-in — I have not read the sources)
The automatic-start path seems to wait on the spawned daemon child stdio rather than on its exit or on a readiness handshake — e.g. a Rust wait_with_output() / Command::output() on the bsk daemon start child. That returns on pipe EOF, not on process exit; the daemon keeps the inherited write handle, so EOF never happens. Using bsk daemon start directly does not take that path, which is why it returns in ~190 ms.
Suggested directions:
- Keep the daemon child off the caller stdio (
Stdio::null()) and/or use a readiness handshake over the existing IPC named pipe; wait on that or on process exit — never on pipe EOF.
- Bound the auto-start wait with a timeout and reuse the error text that the
BSK_AUTO_START=0 path already produces.
- Because of the inherited handles this also leaks into the caller, so keeping every descendant off the caller stdio would stop consumers from hanging even before the wait itself is fixed.
Workarounds
- Consumer side: make sure a daemon is running before the first real command —
bsk daemon start returns in ~200 ms and leaves a detached daemon, so pre-warm once per session (this is what I have patched into the DSH plugin).
- Or set
BSK_AUTO_START=0 and manage the daemon yourself: you get a bounded, actionable error instead of a hang.
- Consumers should settle on process exit rather than pipe EOF; to unblock a caller that is already stuck, kill the daemon.
Related reports
中文摘要
Windows 11(build 26200)+ bsk/扩展 0.3.0:没有 daemon 在跑时,任何非 daemon 子命令(browsers / status / session start)都会永久挂起且零输出——daemon 其实已经在后台起来了(日志有 daemon ready / browser connected),另一个 shell 的 bsk status 能秒回,但第一个进程永不返回。直接跑 bsk daemon start 191 ms 返回并留下健康 daemon;BSK_AUTO_START=0 时同一条命令 86 ms 返回 exit 2 且错误信息清晰。所以卡死点就在「自动拉起 daemon」这条路径的等待逻辑里。已排除:stdio 类型(管道 / 文件重定向 / 真控制台都复现)、环境变量(最小干净环境同样复现)、job object(WMI 外挂 cmd 同样复现)。
更严重的是它会连带冻住调用方:自动拉起的 daemon 继承了调用方的 stdio 句柄,调用方即使超时杀掉 CLI,管道也到不了 EOF(典型的 Windows「等 EOF 而不是等进程退出」陷阱,参见 open-gsd/gsd-browser#62)。实测事故:调用方 120 s 超时杀掉 CLI 后又被额外阻塞 19 分 28 秒,直到有人手动杀掉 daemon 才在同一秒解冻。建议:让 daemon 子进程完全不碰调用方 stdio(Stdio::null())并改为等待进程退出 / IPC 就绪握手,同时给自动拉起路径加超时,并复用 BSK_AUTO_START=0 那句错误提示。
Environment
bsk --version),~/.local/bin/bsk.exe, installed with the officialinstall.ps1bsk browserslists it,session_count: 0)child_process.spawn), WMIWin32_Process.Create+cmd.exebsk update --checkup_to_date(0.3.0 is the current release)Summary
With no daemon running, every bsk command that is not a
daemonsubcommand hangs forever and prints nothing —browsers,status,session start, all of them. The daemon is started in the background (.bsk/daemon.jsonis written, the daemon log showsdaemon readyand thenbrowser connected), and a command issued later from another shell returns in ~100 ms — but the first process never returns and can only be killed.bsk daemon startinvoked directly returns in 191 ms, exit 0, and leaves a healthy daemon.BSK_AUTO_START=0the same cold command returns in 86 ms, exit 2, with the proper actionable error:error: ensure daemon is running: automatic daemon startup is disabled (BSK_AUTO_START=0); run 'bsk daemon start' in the owning host environment with the same BSK_HOME, then retrySo the hang lives exactly in the automatic daemon startup path (
ensure daemon is running) — the code that starts the daemon on demand and then waits for it.Steps to reproduce
bsk daemon stop— afterwardsGet-Process bskis empty.bsk browsers,bsk status,bsk session start --no-focus). It blocks with zero stdout and zero stderr;-vvalso produces nothing, so it blocks before anything is logged to the caller.bsk statusreturns immediately and reports a healthy daemon with the browser connected — the daemon was started successfully, only the waiting client is stuck.Evidence (all observed on the machine above)
bsk browsersfrom PowerShell 5.1 (& bsk ... 2>&1 | Out-String)bsk -vv browsersfrom PowerShellbsk browsersfrom Nodespawn(stdiodefault pipe, and['ignore','pipe','pipe'])bsk browsersfrom a WMI-createdcmd.exewith> out.txt 2> err.txt(outside any job object)bsk browsersviaStart-Process -WindowStyle Hidden(own console)bsk browsersfrom Node with a minimal clean environment (only PATH/SystemRoot/TEMP/USERPROFILE/...)bsk browserswithBSK_AUTO_START=0bsk daemon startdirectlybsk browserswith a warm daemonDaemon log of one cold hang (these lines and then nothing more):
Even after the extension is connected and the daemon is fully ready, the waiting client never proceeds.
Impact — it also freezes the caller
The auto-started daemon appears to inherit the caller stdio handles and keeps them open. For any consumer that spawns
bsk <cmd>with piped stdio and waits for the stream (Nodecloseevent,child_process.execFileSync, Pythonsubprocess.communicate()), the pipe never reaches EOF — so even after the consumer own timeout kills the CLI, the consumer stays blocked forever, an uncancellable hanging tool call.Real incident in a downstream agent harness (DeepSeek Harness plugin
@wxg-prc-cpg/browser-skill-dsh-plugin, which runsbsk <cmd> --jsonwith piped stdio):bsk session start --json; no daemon runningThis is the classic Windows wait-for-EOF-instead-of-exit trap: a descendant holds the write end of the pipe. The same pattern is documented for another daemon CLI in open-gsd/gsd-browser#62 (daemon start leaves the caller inherited stdio handles open, hanging any consumer that uses a pipe).
Hypothesized cause (outside-in — I have not read the sources)
The automatic-start path seems to wait on the spawned daemon child stdio rather than on its exit or on a readiness handshake — e.g. a Rust
wait_with_output()/Command::output()on thebsk daemon startchild. That returns on pipe EOF, not on process exit; the daemon keeps the inherited write handle, so EOF never happens. Usingbsk daemon startdirectly does not take that path, which is why it returns in ~190 ms.Suggested directions:
Stdio::null()) and/or use a readiness handshake over the existing IPC named pipe; wait on that or on process exit — never on pipe EOF.BSK_AUTO_START=0path already produces.Workarounds
bsk daemon startreturns in ~200 ms and leaves a detached daemon, so pre-warm once per session (this is what I have patched into the DSH plugin).BSK_AUTO_START=0and manage the daemon yourself: you get a bounded, actionable error instead of a hang.Related reports
bsk doctorhangs indefinitely when no browser extension is connected (Windows, agent context) #265 —bsk doctorhangs indefinitely when no browser extension is connected (Windows). Its workaround isBSK_AUTO_START=0plus a warm daemon, which matches the hang living in the auto-start path.bsk daemon startauto-detached process exits immediately, extension cannot connect.session stopcannot force-terminate.中文摘要
Windows 11(build 26200)+ bsk/扩展 0.3.0:没有 daemon 在跑时,任何非
daemon子命令(browsers/status/session start)都会永久挂起且零输出——daemon 其实已经在后台起来了(日志有daemon ready/browser connected),另一个 shell 的bsk status能秒回,但第一个进程永不返回。直接跑bsk daemon start191 ms 返回并留下健康 daemon;BSK_AUTO_START=0时同一条命令 86 ms 返回 exit 2 且错误信息清晰。所以卡死点就在「自动拉起 daemon」这条路径的等待逻辑里。已排除:stdio 类型(管道 / 文件重定向 / 真控制台都复现)、环境变量(最小干净环境同样复现)、job object(WMI 外挂 cmd 同样复现)。更严重的是它会连带冻住调用方:自动拉起的 daemon 继承了调用方的 stdio 句柄,调用方即使超时杀掉 CLI,管道也到不了 EOF(典型的 Windows「等 EOF 而不是等进程退出」陷阱,参见 open-gsd/gsd-browser#62)。实测事故:调用方 120 s 超时杀掉 CLI 后又被额外阻塞 19 分 28 秒,直到有人手动杀掉 daemon 才在同一秒解冻。建议:让 daemon 子进程完全不碰调用方 stdio(
Stdio::null())并改为等待进程退出 / IPC 就绪握手,同时给自动拉起路径加超时,并复用BSK_AUTO_START=0那句错误提示。