Skip to content

[Bug][Linux] Synchronous xdg-open in /api/file/open can block the HTTP server (v2.17.30) #540

Description

@nulltrace-r7

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:

  1. Stop nginx to close existing proxy connections.
  2. Stop the separate browser supervisor and CLI-JAW service.
  3. Confirm the old HTTP process and listener have exited.
  4. Start CLI-JAW and verify direct HTTP 200.
  5. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions