Skip to content

SessionEnd hook hangs when broker is unresponsive (missing timeout in sendBrokerShutdown) #245

@clthuang

Description

@clthuang

Bug

The SessionEnd hook (scripts/session-lifecycle-hook.mjs) hangs and gets cancelled by Claude Code when the broker process is unresponsive.

Error message:

SessionEnd hook [node "${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs" SessionEnd] failed: Hook cancelled

Root Cause

sendBrokerShutdown() in scripts/lib/broker-lifecycle.mjs:43-57 creates a socket connection and waits for a data event after sending the shutdown message, but has no timeout:

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);
  });
}

If the broker process:

  • Has the socket file present but is hung/zombie → connect succeeds, data never fires
  • Is slow to respond → Promise waits indefinitely

Claude Code's hook timeout eventually fires and kills the hook with "Hook cancelled".

Note: waitForBrokerEndpoint() at line 24 correctly has a timeoutMs = 2000 parameter. The shutdown call does not.

Suggested Fix

Add a timeout to the shutdown Promise:

export async function sendBrokerShutdown(endpoint) {
  await new Promise((resolve) => {
    const socket = connectToEndpoint(endpoint);
    const timeout = setTimeout(() => {
      socket.destroy();
      resolve();
    }, 3000); // 3s timeout — broker should respond quickly to shutdown
    socket.setEncoding("utf8");
    socket.on("connect", () => {
      socket.write(`${JSON.stringify({ id: 1, method: "broker/shutdown", params: {} })}\n`);
    });
    socket.on("data", () => {
      clearTimeout(timeout);
      socket.end();
      resolve();
    });
    socket.on("error", () => { clearTimeout(timeout); resolve(); });
    socket.on("close", () => { clearTimeout(timeout); resolve(); });
  });
}

Environment

  • Claude Code CLI (macOS arm64, Darwin 25.3.0)
  • Plugin version: codex 1.0.1 (from openai-codex marketplace)
  • Reproduction: use Codex companion during a session, then exit. If broker becomes unresponsive between session and shutdown, the SessionEnd hook hangs.

Impact

Non-blocking (session ends normally), but produces a confusing error message on every affected session exit. Users may think their session data was lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions