Skip to content

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

Closed as duplicate

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.

Activity

  1. pandalaohe commented on Apr 30, 2026

    @pandalaohe

    Hit this on Windows during a Claude Code + custom hooks integration. The Hook cancelled message appears intermittently after Codex review sessions when the broker becomes unresponsive between session and shutdown.

    Local mitigation: registered the plugin's SessionEnd hook explicitly in ~/.claude/settings.json with timeout: 30 (the upstream plugins/codex/hooks/hooks.json declaration didn't auto-merge into our active hook list, so cleanup was never running). That gets the outer 30s budget, but the underlying issue is still that sendBrokerShutdown itself never times out — the 30s is just CC's outer kill, not graceful.

    The suggested 3s socket-level timeout fix in the issue body matches what we'd expect. Happy to test it against our reproduction setup if a PR materializes.

    (Adding a +1 / data-point in case it helps prioritize.)

  2. axisrow commented on Jul 16, 2026

    @axisrow

    Same class as #288 — scenario 2 in the #163 map: the SessionEnd hook hangs on an unresponsive broker because sendBrokerShutdown has no timeout. Out of scope for PR #490 (the timer lives inside the broker, the hang is client-side). The fix is a client-side bounded timeout. See #163.

  3. yuanchen8911 commented on Aug 10, 2026

    @yuanchen8911

    Cross-reference: #612 and PR #623 make shutdown fail closed on ambiguous outcomes, but intentionally leave the accepted-and-never-replies timeout out of scope. This issue remains the tracker for bounding that exchange and destroying the socket within the SessionEnd hook budget. Duplicate report: #288.

  4. dkundel-openai commented on Aug 11, 2026

    @dkundel-openai
    Collaborator

    Duplicate of #288.

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

    duplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions