Summary
On Linux, CLI-JAW's Web UI and direct localhost HTTP endpoint became unresponsive while the Node server process remained alive.
The installed v2.17.30 build contains a synchronous xdg-open call in the file-open handler. Process and socket observations strongly suggest that this call was blocking normal HTTP processing while waiting for an external desktop application.
This is an observed incident with runtime evidence, not yet a clean-environment minimal reproduction. The exact initiating UI action and file target were not captured.
Environment
- CLI-JAW:
2.17.30; the installed package was not locally patched.
- Node.js:
v24.19.0.
- OS: Ubuntu
24.04.4 LTS, x86_64.
- Kernel:
6.8.0-138-generic.
- CLI-JAW runs as a non-root user through a systemd user service and
xvfb-run.
- Startup includes
jaw serve --port 3458 --no-open.
- Chrome is installed;
xdg-open was observed launching a Chrome child.
- A self-managed nginx reverse proxy and a separate browser supervision script are present. However, the HTTP failure was also confirmed directly on the server at
127.0.0.1:3458, bypassing the proxy and SSH forwarding.
- Port
3458 was explicitly configured because the default port 3457
was already used by another CLI-JAW instance under a different OS user.
All runtime observations in this report refer to the instance on 3458.
Expected behavior
Opening a file or folder through the system desktop application should not prevent unrelated HTTP requests from being processed.
If the opener cannot complete, the file-open operation should report an appropriate outcome without making the entire Web UI unresponsive.
Relevant code
Installed build: dist/src/routes/messaging.js, line 173:
execFileSync('xdg-open', [target.openedPath]);
No timeout or other options are supplied at this call site.
The corresponding POST /api/file/open handler is in the v2.17.30 source.
Node.js documents that synchronous child-process methods block the event loop. This makes a long-lived desktop opener a potential server-wide availability problem, rather than just a slow file-open response.
Observed evidence
During the incident:
- The same Node process continued to own the HTTP listener.
- Direct
GET / requests to http://127.0.0.1:3458/ timed out with curl exit code 28 and no HTTP response.
- The listener's
Recv-Q increased from 249 to 277, then reached 512, with Send-Q shown as 511.
- The main thread was sleeping in
epoll_pwait, rather than continuously consuming CPU.
- An
xdg-open process was a direct child of the HTTP server process and remained alive for hours, waiting for a Chrome child.
- The file-opening Chrome was distinct from the managed browser owning the CDP listener.
Additional /proc and Unix-socket inspection showed:
HTTP listening socket:
FD 31, registered in epoll FD 15
Main thread's current wait:
epoll_pwait on epoll FD 37
FDs monitored by epoll FD 37:
Included Unix sockets whose peer endpoints were held as
stdout/stderr by a direct child xdg-open and related helpers.
This is consistent with a synchronous child-process wait taking over the server thread. A JavaScript/native call stack was not captured, so the precise executing call site remains an inference supported by the installed code and runtime observations.
Recovery observations and workaround
A targeted termination of the file-opening Chrome caused that Chrome and its parent xdg-open to exit, but did not restore HTTP availability during the subsequent checks.
A later snapshot showed a new direct-child xdg-open with different socket identities. The source of that later invocation was not captured; queued requests or another caller remain possible explanations.
HTTP service was restored after a controlled sequence:
- Stop nginx to close existing proxy connections.
- Stop the separate browser supervisor and CLI-JAW service.
- Confirm the old HTTP process and listener have exited.
- Start CLI-JAW and verify direct HTTP
200.
- Resume browser supervision, then nginx.
After recovery, the HTTP listener's Recv-Q was 0, direct and proxied requests returned 200, and the Web UI displayed normally.
As a temporary workaround, nginx now rejects /api/file/open requests before they reach CLI-JAW. This does not modify the package or protect callers that bypass nginx.
Reproduction status
The exact original UI action and file target were not recorded. The failure has not been deliberately rerun on the recovered server or reproduced in a clean environment.
A suggested regression test, not performed as part of this report, would use an isolated server process and a controlled long-running opener stub. After invoking the file-open handler with a valid temporary fixture, another HTTP request should remain responsive while the opener is still running.
The test should clean up the stub and cover opener launch failures as well.
Suggested fix direction
Please consider asynchronous opener dispatch with explicit launch-error handling and appropriate stdio/lifecycle management, so the HTTP server does not wait synchronously for a desktop application's lifetime.
An option to disable server-side file opening in non-desktop deployments may also be useful, but avoiding server-wide blocking is the main concern.
Version cross-check
The runtime incident described here was observed on v2.17.30.
A review of the official v2.17.38 tagged source confirmed that
src/routes/messaging.ts still uses
execFileSync('xdg-open', [target.openedPath]) in the Linux
file-open handler.
This is a source-level check, not a runtime reproduction on
v2.17.38 or a verification of that version's npm package contents.
References
Summary
On Linux, CLI-JAW's Web UI and direct localhost HTTP endpoint became unresponsive while the Node server process remained alive.
The installed v2.17.30 build contains a synchronous
xdg-opencall in the file-open handler. Process and socket observations strongly suggest that this call was blocking normal HTTP processing while waiting for an external desktop application.This is an observed incident with runtime evidence, not yet a clean-environment minimal reproduction. The exact initiating UI action and file target were not captured.
Environment
2.17.30; the installed package was not locally patched.v24.19.0.24.04.4 LTS, x86_64.6.8.0-138-generic.xvfb-run.jaw serve --port 3458 --no-open.xdg-openwas observed launching a Chrome child.127.0.0.1:3458, bypassing the proxy and SSH forwarding.3458was explicitly configured because the default port3457was already used by another CLI-JAW instance under a different OS user.
All runtime observations in this report refer to the instance on
3458.Expected behavior
Opening a file or folder through the system desktop application should not prevent unrelated HTTP requests from being processed.
If the opener cannot complete, the file-open operation should report an appropriate outcome without making the entire Web UI unresponsive.
Relevant code
Installed build:
dist/src/routes/messaging.js, line 173:No timeout or other options are supplied at this call site.
The corresponding
POST /api/file/openhandler is in the v2.17.30 source.Node.js documents that synchronous child-process methods block the event loop. This makes a long-lived desktop opener a potential server-wide availability problem, rather than just a slow file-open response.
Observed evidence
During the incident:
GET /requests tohttp://127.0.0.1:3458/timed out with curl exit code28and no HTTP response.Recv-Qincreased from249to277, then reached512, withSend-Qshown as511.epoll_pwait, rather than continuously consuming CPU.xdg-openprocess was a direct child of the HTTP server process and remained alive for hours, waiting for a Chrome child.Additional
/procand Unix-socket inspection showed:This is consistent with a synchronous child-process wait taking over the server thread. A JavaScript/native call stack was not captured, so the precise executing call site remains an inference supported by the installed code and runtime observations.
Recovery observations and workaround
A targeted termination of the file-opening Chrome caused that Chrome and its parent
xdg-opento exit, but did not restore HTTP availability during the subsequent checks.A later snapshot showed a new direct-child
xdg-openwith different socket identities. The source of that later invocation was not captured; queued requests or another caller remain possible explanations.HTTP service was restored after a controlled sequence:
200.After recovery, the HTTP listener's
Recv-Qwas0, direct and proxied requests returned200, and the Web UI displayed normally.As a temporary workaround, nginx now rejects
/api/file/openrequests before they reach CLI-JAW. This does not modify the package or protect callers that bypass nginx.Reproduction status
The exact original UI action and file target were not recorded. The failure has not been deliberately rerun on the recovered server or reproduced in a clean environment.
A suggested regression test, not performed as part of this report, would use an isolated server process and a controlled long-running opener stub. After invoking the file-open handler with a valid temporary fixture, another HTTP request should remain responsive while the opener is still running.
The test should clean up the stub and cover opener launch failures as well.
Suggested fix direction
Please consider asynchronous opener dispatch with explicit launch-error handling and appropriate stdio/lifecycle management, so the HTTP server does not wait synchronously for a desktop application's lifetime.
An option to disable server-side file opening in non-desktop deployments may also be useful, but avoiding server-wide blocking is the main concern.
Version cross-check
The runtime incident described here was observed on v2.17.30.
A review of the official v2.17.38 tagged source confirmed that
src/routes/messaging.tsstill usesexecFileSync('xdg-open', [target.openedPath])in the Linuxfile-open handler.
This is a source-level check, not a runtime reproduction on
v2.17.38 or a verification of that version's npm package contents.
References