openai / openai/codex-plugin-cc

`sendBrokerShutdown` has no timeout — SessionEnd hook can hang indefinitely

Open Beginner friendly
#288 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
33.3k
Forks
2.3k
PR merge metrics
No merged PRs in 30d

Description

Summary

sendBrokerShutdown in plugins/codex/scripts/lib/broker-lifecycle.mjs awaits a Promise that resolves only on socket data, error, or close events. If the broker accepts the connection but stops responding mid-RPC (no data, no error, no close), the Promise never resolves and the SessionEnd hook hangs indefinitely.

Affected code

plugins/codex/scripts/lib/broker-lifecycle.mjs:43-57 (v1.0.3)

export async function sendBrokerShutdown(endpoint) {
  await new Promise((resolve) => {
    const socket = connectToEndpoint(endpoint);
    socket.setEncoding("utf8");
    socket.on("connect", () => {
      socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
    });
    socket.on("data", () => {
      socket.end();
      resolve();
    });
    socket.on("error", resolve);
    socket.on("close", resolve);
  });
}

Asymmetry with startup path

waitForBrokerEndpoint in the same file (line 24) explicitly takes timeoutMs = 2000 and bounds its wait loop. Startup path is timed; shutdown path isn't. Same module — looks like an oversight rather than intent.

Observed symptoms

Running Claude Code in unattended (cron/launchd) invocations on macOS:

  • Multiple sessions hung at SessionEnd after content rendered cleanly. One held its lock 19.5 hours before something external (a new Claude Code session creating a fresh broker socket) unwedged it.
  • In a separate session the harness's hook timeout fired with Hook cancelled and exit 1 — content was already delivered, but the misleading exit code surfaced as a failure in launchd telemetry.
  • Correlation suggests macOS DarkWake during long-running SSE streams wedges the broker. We mitigated with caffeinate -ims on the wrapper, but the shutdown await still hangs when a wedge does happen.

Proposed fix

Mirror the timeout pattern used in waitForBrokerEndpoint:

export async function sendBrokerShutdown(endpoint, timeoutMs = 5000) {
  await new Promise((resolve) => {
    const socket = connectToEndpoint(endpoint);
    let settled = false;
    const finish = () => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      try { socket.destroy(); } catch {}
      resolve();
    };
    const timer = setTimeout(finish, timeoutMs);
    socket.setEncoding("utf8");
    socket.on("connect", () => {
      socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
    });
    socket.on("data", finish);
    socket.on("error", finish);
    socket.on("close", finish);
  });
}

5s is generous for a local socket RPC and can be tuned. Caller in session-lifecycle-hook.mjs:99 requires no change.

Environment

  • Plugin: @openai/codex-plugin-cc v1.0.3
  • Platform: macOS Darwin 25.4 (arm64)
  • Node: >= 18.18 (per engines)
  • Trigger: launchd-scheduled claude --agent ... -p ... --permission-mode bypassPermissions heartbeat invocations

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in plugins/codex/scripts/lib/broker-lifecycle.mjs:43-57 and compare sendBrokerShutdown with the timeout pattern in waitForBrokerEndpoint at line 24. Check the caller in session-lifecycle-hook.mjs:99 to confirm its contract. Done means a stalled local broker cannot leave the SessionEnd hook waiting indefinitely, while normal data, error, and close paths still complete.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.