Plugin hooks never run when the plugin is installed from a directory-source marketplace

Status Closed — not planned
Reported on v2.1.233
Maintainer reply None cached
Activity 1 comment · opened Aug 15, 2026 · closed Aug 15, 2026

Summary

A plugin installed from a directory-source marketplace reports Status: ✔ enabled,
ships its hooks/hooks.json, and populates its versioned cache dir — but its hooks never
run. The same plugin at the same version installed from a github-source marketplace
runs them normally.

Nothing on screen indicates the difference, so the standard local plugin-development loop
(add your repo as a directory marketplace → install → test) silently exercises a build with
no hooks.

Environment

  • Claude Code 2.1.233
  • Container: node:22-bookworm-slim, linux/arm64 (Docker Desktop 29.1.3 on macOS)
  • Node 22.23.2
  • Plugin under test: cli-dispatch v4.18.0 (https://github.com/rbinar/cli-dispatch)
  • Auth: API-key style (ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN)

Reproduction

The plugin registers a SessionStart hook:

{
  "hooks": {
    "SessionStart": [
      { "matcher": "startup", "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/policy-inject.mjs\"" }] }
    ]
  }
}

For the test, that script is replaced with a shim that appends to a canary file. The shim
is self-tested by running it directly before every session
, so a broken shim can never be
mistaken for a hook that did not fire:

#!/usr/bin/env node
import { appendFileSync } from "node:fs"
try { appendFileSync("/home/dev/CANARY.txt", "FIRED\n") } catch {}
process.exit(0)

A — github source → hook runs

claude plugin marketplace add rbinar/cli-dispatch
claude plugin install cli-dispatch@cli-dispatch -y
# install the canary shim over scripts/policy-inject.mjs, verify it writes when run directly
rm -f /home/dev/CANARY.txt
claude -p "say OK"
ls /home/dev/CANARY.txt      # present

Result: FIRED on 2/2 runs.

B — directory source → hook does not run

Same container, same session state, same plugin version, freshly cleared plugin cache:

claude plugin marketplace remove cli-dispatch
rm -rf ~/.claude/plugins/cache/cli-dispatch
claude plugin marketplace add /home/dev/cli-dispatch-repo   # local clone, checked out at v4.18.0
claude plugin install cli-dispatch@cli-dispatch -y
# same canary shim, same self-test
rm -f /home/dev/CANARY.txt
claude -p "say OK"
ls /home/dev/CANARY.txt      # absent

Result: ABSENT on 2/2 runs.

Expected vs actual

Expected: a plugin's hooks run whenever the plugin is installed and enabled, regardless
of how its marketplace was sourced.

Actual: hooks run only for github-sourced marketplaces. Directory-sourced installs are
indistinguishable in every surface I could inspect, yet their hooks are inert.

What I ruled out

Each of these was tested and is not the cause:

  • Print vs interactive mode. Reproduced in both claude -p and a real interactive

session under a pty (script -qec claude /dev/null) with onboarding pre-cleared.

  • Directory trust. Setting hasTrustDialogAccepted: true for the working directory in

~/.claude.json changed nothing.

  • Plugin version. Pinned both arms to v4.18.0 (git checkout v4.18.0 for the directory

arm). Also seen with v4.20.0 on the directory arm.

  • Hook command interpreter. Replacing node … with a plain /bin/bash -c 'echo … >> file'

hook in the plugin's hooks.json also did not fire under a directory source.

  • Plugin disabled / not installed. claude plugin list reports

Status: ✔ enabled in both arms; installed_plugins.json has the correct installPath
and version; hooks/hooks.json is present in the cache dir in both arms.

  • SessionStart not firing at all. A SessionStart hook registered in

~/.claude/settings.json (not via a plugin) does fire in the very same session where
the plugin's hook does not — so the event itself is delivered.

  • A broken canary shim. Every reported run self-tested the shim first. (One earlier run

of mine did produce a false negative from a shim with a syntax error; that run was
discarded and is not part of the results above.)

Impact

Plugin authors developing locally add their working tree as a directory marketplace — it is
the only way to test uncommitted changes. In that loop, hooks are silently dead: a
SessionStart hook can appear correct, be enabled, and never execute, with no diagnostic.
Any hook behavior verified this way is unverified in practice.

A warning at install time, or in claude plugin list, would be enough if this is intended
rather than a bug.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗