vercel / vercel/vercel-plugin

SessionEnd cleanup hook gets cancelled on exit — hooks.json ships no `timeout`, and Claude Code's default for SessionEnd hooks is only 1.5s

Open Beginner friendly
#122 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
287
Forks
58
Avg merge
1d 1h
Merged PRs (30d)
17

Description

Problem

On session exit, the plugin's SessionEnd hook is reported as failed on every substantive session close:

SessionEnd hook [node "${CLAUDE_PLUGIN_ROOT}/hooks/session-end-cleanup.mjs"] failed: Hook cancelled

This is not just a display artifact — the cleanup genuinely does not run. The session-scoped dedup temp files (<tmpdir>/vercel-plugin-<sessionId>-*, the claim dir / seen-skills state described in the plugin's own dedup contract) are left behind for closed sessions:

$ ls /tmp | grep vercel-plugin
vercel-plugin-1ec5a71c-...-greenfield.txt      # session closed at 22:10 — cleanup never ran
vercel-plugin-1ec5a71c-...-likely-skills.txt

Root cause

hooks/hooks.json declares the SessionEnd hook with no timeout field (current main, blob 3c6562c).

In Claude Code (verified in the 2.1.214 bundle), executeSessionEndHooks invokes the hook runner with a default timeoutMs of 1500 for SessionEnd specifically. Per hook, the budget is hook.timeout * 1000 if configured, else that 1500ms default. A hook that exceeds its budget is aborted (ABORT_ERR → "Hook cancelled").

session-end-cleanup.mjs itself is fast — standalone it exits 0 in ~50ms:

$ echo '{"session_id":"x"}' | node hooks/session-end-cleanup.mjs   # 0.047s, exit 0

But at a real session close the 1.5s window is easily consumed before the hook finishes: session teardown (notably with "tui": "fullscreen") plus sibling SessionEnd hooks run in the same instant. anthropics/claude-code#70465 (open) documents this exact class — a sub-second exit 0 SessionEnd hook cancelled alongside busy siblings. In my setup, user-configured SessionEnd hooks that declare explicit timeout values (10–30s) run to completion every close; the plugin's hook — the only SessionEnd hook without a timeout — is the only one that gets cancelled.

There's a second cost of not declaring a timeout: Claude Code derives its SessionEnd force-exit grace from the max configured timeout across all SessionEnd hooks, clamped to [1.5s, 60s]. For users whose only SessionEnd hook is this plugin's, everything collapses to the 1.5s floor.

Suggested fix

Declare a timeout on the SessionEnd hook (same one-field-per-hook shape as the shell fix proposed in #79):

     "SessionEnd": [
       {
         "hooks": [
           {
             "type": "command",
-            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-end-cleanup.mjs\""
+            "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-end-cleanup.mjs\"",
+            "timeout": 15
           }
         ]
       }
     ]

This has no latency cost in the normal case — the hook still exits in milliseconds; the timeout only widens the abort window so teardown contention can't kill it. In the same environment, every SessionEnd hook that declares an explicit timeout completes on every close; only this one — the only one relying on the 1.5s default — gets cancelled.

timeout is a documented per-hook field in the hooks reference, and the plugin's own doctor already reasons about hook timeout risk, so this seems like the intended knob.

Environment

  • Claude Code 2.1.214, "tui": "fullscreen"
  • vercel-plugin 0.44.0 (marketplace install)
  • Linux x86_64 (Arch-based)

Related

  • anthropics/claude-code#70465 — open: SessionEnd hooks killed on exit, fast siblings included, no configurable grace
  • anthropics/claude-code#63495 — closed: the display-only variant (hook exits 0 but message still shown). Distinct from this report: here the leftover temp files prove the cleanup really doesn't run.
  • #79 — same file, precedent for adding per-hook fields to hooks.json

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 hooks/hooks.json and inspect the SessionEnd hook for session-end-cleanup.mjs, then review the hook timeout reference and the precedent in #79. Verify the configured timeout prevents cancellation during session teardown and run the standalone node command from the report to confirm the cleanup hook exits successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js
Domain
tooling
Issue type
Bug
Difficulty
1/5
Estimated time
Under an hour
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.