openclaw - ✅(Solved) Fix macOS exec host can deadlock on exec-approvals.sock because JSONL client never half-closes the write side [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#59633Fetched 2026-04-08 02:42:13
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2closed ×1locked ×1referenced ×1

On macOS 2026.4.1, the mac-cli-host -> OpenClaw.app exec-host path can hang indefinitely even though the app is running, the socket is listening, and TCC permissions are already granted.

After tracing this end to end on April 2, 2026, the issue appears to be in the JSONL socket client used by the official CLI. The client writes one line but does not half-close the socket write side, while the app-side socket server reads with FileHandle.read(upToCount:) inside readLineFromHandle(...). In practice this can deadlock the request/response exchange.

Root Cause

Root Cause Hypothesis

Fix Action

Fix / Workaround

Verification After Patch

After this one-line local patch, all of the following started working without changing the gateway topology:

PR fix notes

PR #59635: Fix macOS exec-host JSONL socket deadlock by half-closing the request stream

Description (problem / solution / changelog)

Summary

This fixes a macOS exec-host deadlock on exec-approvals.sock by half-closing the client write side after sending the single JSONL request line.

Root Cause

requestJsonlSocket() was sending the request with client.write(...) and leaving the socket write side open.

On the macOS app side, the exec-host socket server reads with FileHandle.read(upToCount:) inside readLineFromHandle(...). In practice, that can leave the server waiting for more input / EOF, while the client is waiting for a response, so the request hangs.

Switching the client send to client.end(...) matches the one-request / one-response JSONL protocol and avoids the deadlock.

Changes

  • replace client.write(${payload}\n) with client.end(${payload}\n)
  • add a regression test that only replies after the client half-closes the write side

Verification

Local targeted test:

pnpm exec vitest run --config vitest.unit.config.ts src/infra/jsonl-socket.test.ts

Result:

  • 1 test file passed
  • 3 tests passed

Related

  • Closes #59633

Changed files

  • src/infra/jsonl-socket.test.ts (modified, +38/-0)
  • src/infra/jsonl-socket.ts (modified, +2/-1)

PR #59748: fix: Half-close JSONL socket write side to prevent deadlock

Description (problem / solution / changelog)

Description

This PR fixes a deadlock issue in the JSONL socket client that prevented exec approval communication on macOS.

Problem

The JSONL socket client was using write() which leaves the write side open, causing the server to wait indefinitely for more data. This results in a deadlock where the server never completes the read/respond cycle.

Solution

Changed write() to end() to properly signal end-of-stream to the server, which half-closes the write side of the socket.

Root Cause

The server uses FileHandle.read(upToCount:) in a loop and expects the client to signal when it's done sending. Without half-close, the server blocks forever waiting for more input.

Impact

Fixes exec approval socket communication on macOS when called from remote gateway via pc-steward -> mac-cli-host path.

Testing

The fix was verified by the issue reporter with the following scenarios working after the patch:

  • \OCI -> pc-steward -> mac-cli-host -> system.run /usr/bin/uname -a\
  • \OCI -> pc-steward -> mac-cli-host -> system.run /bin/cat /etc/hosts\
  • \OCI -> pc-steward -> mac-cli-host -> system.run /usr/sbin/screencapture -x ...\

Fixes: #59633

Type of Change

  • Bug fix
  • Documentation
  • New feature
  • Breaking change

Checklist

  • My code follows the code style of this project
  • My changes do not introduce new warnings
  • I have tested my changes (verified by issue reporter)

Changed files

  • src/infra/jsonl-socket.ts (modified, +5/-1)

Code Example

gateway timeout after 12000ms

---

client.connect(socketPath, () => {
  client.write(`${payload}\n`);
});

---

client.connect(socketPath, () => {
  client.end(`${payload}\n`);
});
RAW_BUFFERClick to expand / collapse

Summary

On macOS 2026.4.1, the mac-cli-host -> OpenClaw.app exec-host path can hang indefinitely even though the app is running, the socket is listening, and TCC permissions are already granted.

After tracing this end to end on April 2, 2026, the issue appears to be in the JSONL socket client used by the official CLI. The client writes one line but does not half-close the socket write side, while the app-side socket server reads with FileHandle.read(upToCount:) inside readLineFromHandle(...). In practice this can deadlock the request/response exchange.

Environment

  • OpenClaw CLI: 2026.4.1 (da64a97)
  • OpenClaw.app: 2026.4.1
  • macOS: Darwin 25.4.0 / macOS 15.4
  • Gateway topology: unchanged remote gateway + pc-steward -> exec(host=node) -> mac-cli-host
  • mac-cli-host configured with OPENCLAW_NODE_EXEC_HOST=app

Symptoms

When the request goes through the macOS app exec host path:

  • system.run via mac-cli-host times out
  • static screenshot commands like screencapture -x ... time out
  • the app is running and ~/.openclaw/exec-approvals.sock is listening
  • direct app-node system.run can still work, which helps isolate the problem to the CLI -> app socket layer

Typical upstream-visible failure from the gateway side:

gateway timeout after 12000ms

Reproduction

  1. Start OpenClaw.app.
  2. Start mac-cli-host with OPENCLAW_NODE_EXEC_HOST=app.
  3. Send a valid type: "exec" JSONL request to ~/.openclaw/exec-approvals.sock.
  4. Keep the client socket write side open after writing the line.
  5. Observe that the app does not reply and the caller times out.

I reproduced this with a direct socket probe and also through the real remote path:

  • OCI -> pc-steward -> mac-cli-host -> OpenClaw.app

Root Cause Hypothesis

The server side in:

  • apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift

uses readLineFromHandle(...), which calls FileHandle.read(upToCount:) in a loop.

The client side in:

  • src/infra/jsonl-socket.ts

currently does:

client.connect(socketPath, () => {
  client.write(`${payload}\n`);
});

On my machine, that leaves the write side open and the server never completes the read/respond cycle. The same exact request immediately succeeds if the client half-closes after sending.

Minimal Fix That Worked Locally

Changing the client send from write(...) to end(...) fixed the issue immediately:

client.connect(socketPath, () => {
  client.end(`${payload}\n`);
});

Verification After Patch

After this one-line local patch, all of the following started working without changing the gateway topology:

  • OCI -> pc-steward -> mac-cli-host -> system.run /usr/bin/uname -a
  • OCI -> pc-steward -> mac-cli-host -> system.run /bin/cat /etc/hosts
  • OCI -> pc-steward -> mac-cli-host -> system.run /usr/sbin/screencapture -x ...

The screenshot file was successfully created and visually verified.

Why I think this is upstream-safe

  • This socket is JSONL request/response, not a long-lived bidirectional stream.
  • The client only sends one request line and then waits for one response line.
  • Half-closing the write side matches that protocol shape and avoids the deadlock.

Relevant Files

  • docs/platforms/mac/xpc.md
  • apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift
  • src/infra/jsonl-socket.ts
  • src/infra/exec-host.ts

If useful, I can also provide the exact direct-socket repro payload I used to confirm the difference between write() and shutdown(SHUT_WR) / end().

extent analysis

TL;DR

Changing the client send from write(...) to end(...) in src/infra/jsonl-socket.ts likely fixes the indefinite hang issue.

Guidance

  • Verify that the issue is indeed caused by the client not half-closing the socket write side after sending the request.
  • Update the src/infra/jsonl-socket.ts file to use client.end(${payload}\n); instead of client.write(${payload}\n); to ensure the write side is half-closed after sending the request.
  • Test the updated client with the provided reproduction steps to confirm the fix.
  • Review the apps/macos/Sources/OpenClaw/ExecApprovalsSocket.swift file to ensure that the server-side implementation is correct and handles the half-closed socket write side as expected.

Example

client.connect(socketPath, () => {
  client.end(`${payload}\n`);
});

This code snippet shows the updated client send method that half-closes the socket write side after sending the request.

Notes

The provided fix assumes that the client only sends one request line and waits for one response line, which matches the JSONL request/response protocol shape. However, if the protocol requires a long-lived bidirectional stream, a different approach may be needed.

Recommendation

Apply the workaround by changing the client send method to end(...) to avoid the deadlock issue. This fix is considered upstream-safe since it matches the expected protocol shape and avoids the deadlock.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix macOS exec host can deadlock on exec-approvals.sock because JSONL client never half-closes the write side [2 pull requests, 1 participants]