Repository navigation
Fix six open upstream issues in the hookify and plugin-dev plugins - #4
Merged
Merged
Conversation
…ample.py The example script pointed to the old docs.anthropic.com domain while every other reference in the repo uses code.claude.com/docs/en/...
Eight bundled skills declare a title-cased `name` with spaces, e.g.
name: Skill Development # plugins/plugin-dev/skills/skill-development/
name: MCP Integration # plugins/plugin-dev/skills/mcp-integration/
The Agent Skills specification requires the `name` field to contain only
lowercase alphanumerics and hyphens, and to match the parent directory
name. Both constraints are violated by all eight, so `skills-ref validate`
rejects them.
The two skills outside plugin-dev/hookify already follow the spec —
`claude-opus-4-5-migration` and `frontend-design` — so this changes the
eight outliers to the convention the repository already uses elsewhere.
Only the frontmatter `name` field is touched. The prose headings in
plugins/plugin-dev/README.md and the `#` titles inside each SKILL.md keep
their human-readable capitalization, since neither is a skill identifier.
Spec: https://agentskills.io/specification#name-field
…) and stop false-flagging valid agents Two defects made the validator fail on plugin-dev's own agent files (anthropics#83803): 1. Under `set -e`, `((warning_count++))` / `((error_count++))` return a nonzero status when the counter was 0, so the script died at the first warning or error instead of finishing the run. Increments now use `count=$((count + 1))`, which always returns 0. 2. Field extractions like `TOOLS=$(... | grep '^tools:' ...)` aborted the script under `set -e` when the field was absent (grep exits 1 on no match), instead of reporting the missing field. They now end in `|| true`. 3. The description check only read the first physical line of the `description:` value, so multi-line descriptions with <example> blocks (as in plugin-dev's own agents) were false-flagged as missing examples. The extraction now captures the full multi-line value. Adds validate-agent.test.sh: plugin-dev's own agents must exit 0, a warning-only file must complete with exit 0, and an invalid file must still exit 1 with all errors reported. No-Verification-Needed: standalone shell script in the public repo; driven end-to-end directly plus new regression harness
… validate-hook-schema.sh
Reuse the existing _DOC_EXTS path filter for the four XSS-family substring rules so documentation examples do not emit security warnings. Add regression coverage for every documentation extension while keeping the existing executable-source detections intact.
Every agent's description was a single unquoted scalar containing dialogue lines like Daisy: "..." / Assistant: "...", which YAML parses as an illegal nested mapping. That leaves the agent loading with empty frontmatter (name/description/model/color all silently dropped), so it can't be selected by description-based routing. silent-failure-hunter.md was reported (anthropics#86748); the same defect was present in the other five agents in this plugin, so all six are converted to a `description: |` block scalar. Body content and description text are unchanged, only the frontmatter encoding. Fixes anthropics#86748 Co-Authored-By: Claude Sonnet 5 <[email protected]>
…h with a quoted path
The Stop hook in ralph-wiggum and the SessionStart hooks in
learning-output-style and explanatory-output-style gave a bare, unquoted
"${CLAUDE_PLUGIN_ROOT}/....sh" as the hook command. Shell-form hook
commands are expanded by the shell from the CLAUDE_PLUGIN_ROOT env var,
so a plugin root containing a space word-splits and the hook exits 127
("not found") with no visible effect; the bare form also relies on the
script's exec bit and, on Windows, on the CLI prepending an interpreter.
Invoke the scripts as `bash "${CLAUDE_PLUGIN_ROOT}/....sh"`, the form
security-guidance already uses, and bump the three plugins to 1.0.1 so
existing installs pick the change up on update (the plugin.json version
is what the updater compares).
Refs anthropics#95673, anthropics#78490.
…es in plugin scripts Applied from anthropics#84711 without the ralph-wiggum stop-hook.sh change (rejecting any transcript path containing '..' or that is a symlink would end the loop for legitimate setups).
… reach; mask credential values in review feedback and session state Applied from anthropics#96434 (both commits, squashed). Unit tests in plugins/security-guidance/tests pass.
…es as UTF-8 A rule whose frontmatter value contained '---' (a regex such as a---b) was cut at that point and the rest moved into the message, because the file was split on the bare string. The closing delimiter is now a line holding only '---'. Rule files and transcripts were opened with the locale's encoding, so on Windows (cp1252) a rule with non-ASCII text was skipped with a decode error. They are read as UTF-8; rule files also accept a BOM. Addresses upstream issues 93028 and 89026. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
… called The hooks imported 'hookify.core...' after putting the plugin's parent on sys.path, which only works when the install directory is named 'hookify'. The plugin cache names it by version, so every hook failed with 'No module named hookify' and hookify did nothing. Register the plugin root as the package's search path instead; fall back to the script's own location when CLAUDE_PLUGIN_ROOT is unset. Addresses upstream issue 81448. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
An install path with a space (macOS user folders, for one) split the unquoted
${CLAUDE_PLUGIN_ROOT} into two arguments, so every hook failed to start.
Addresses upstream issue 78490 for hookify. The three plugins that run a script
directly (ralph-wiggum and the two output styles) are not touched here.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
Only .claude/hookify.*.local.md files are read, but the examples shipped without the 'hookify.' prefix, so copying one as-is silently did nothing. Rename them and say so in the writing-rules skill. Addresses upstream issue 79143. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
…ter block The sed range reopened at every later '---' line, so a horizontal rule in the body made the text after it part of the frontmatter. Read from the opening '---' on the first line to the next '---' line, and tolerate CRLF. Addresses upstream issue 92798. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
Run the hook scripts from an installed copy in a directory not named hookify, under a path with a space, and under a non-UTF-8 default encoding. Each test fails on the code before the fixes. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
Each hook ran 'python3 <script>'. When the script cannot be opened, python3 exits 2, and a hook that exits 2 blocks: every prompt (UserPromptSubmit) and every tool call (PreToolUse) is refused, with no way out from inside the session. This happens when the plugin directory moves under a running session, as with an org-synced plugin re-synced to a new path. The commands now check that the script exists; if not, they print the path to stderr and exit 1, a non-blocking error Claude Code shows instead of a block. The hookify scripts themselves only ever exit 0, so no intended block is lost. Mitigates upstream issue 98839 for hookify. The root cause there, stale hook paths in a live Cowork session and exit 2 from a missing file being treated as a block, is in Claude Code itself. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
adri22235
marked this pull request as ready for review
October 2, 2026 16:14
Resolves the conflict in parse-frontmatter.sh: keep the field-name validation from page 1 (upstream PR 84711) and this branch's extraction of the leading frontmatter block. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes bugs reported as open issues in
anthropics/claude-codethat live in files of this repo (the bundledhookifyandplugin-devplugins). Each issue was reproduced first; one commit per issue plus a test commit.---(a regex likea---b) was cut there and the rest moved into the message---No module named 'hookify'on every hook: the plugin cache names the directory by version, nothookifyhookifypackage instead of relying on the parent being onsys.path${CLAUDE_PLUGIN_ROOT}inhooks.jsonhookify.prefix, so copying one as-is never loadedparse-frontmatter.shtreated text after a---in the body as frontmatterpython3cannot open the script and exits 2, which blocks every prompt and tool callThe 98839 change only protects hookify. The root cause in that issue (a Cowork session keeping stale hook paths after an org plugin re-syncs, and exit 2 from a missing file being treated as a block) is in Claude Code itself. The hookify scripts only ever exit 0, so no intended block is lost.
Reproduced but not fixed: upstream issue 84745 (PreToolUse does not filter undefined events). I could not make a rule of another event fire on a
Readcall, so there was no failure to fix. Other hookify issues such as 20747 (warnings do not reach the model) are about how Claude Code treatssystemMessageand are not changeable from this repo.Not touched: the unquoted
${CLAUDE_PLUGIN_ROOT}in the ralph-wiggum and the two output-stylehooks.jsonfiles (issue 78490 covers them too). The first PR in this fork already changes those three, so they are left out here to avoid a conflict.Overlap with open upstream PRs: 81672 and 81670 fix the same import, quoting and example-name issues. 81672 puts the package registration in a shared
_bootstrap.py, a cleaner layout than the per-hook block here.Test plan
python3 -m unittest discover -s plugins/hookify/tests -p 'test_*.py': 15 pass. Against the code before the fixes the same tests fail, so each one catches its bug (the missing-script test fails for all four hooks on the previoushooks.json)plugins/plugin-dev/skills/plugin-settings/scripts/parse-frontmatter.test.sh: 6 pass (5 fail before)pretooluse.pyfromcache/hookify/1.0.0and from a path with a space, and the UserPromptSubmit command with its plugin directory gone (exit 1 undershandbash, was 2)hooks.jsonstill valid JSONPYTHONUTF8=0 LC_ALL=Cvalidate-hook-schema.shreports an error on thishooks.json, as it does on the original: it does not understand the wrapped{"hooks": {...}}format (fixed by the first PR in this fork)🤖 Generated with Claude Code
https://claude.ai/code/session_01LoLAv3aLaRJocsRMcjFbpv