Diagnosing hooks
Use to diagnose and fix ZCode hook configuration problems in the ZCode client. Applies when a hook does not trigger, an event name is wrong, a matcher does not match a tool name, a script is not executable, template variables are not expanded, a timeout unit is mistaken (seconds versus milliseconds), the command and process field styles are mixed, a hook's JSON output fails validation, a hook blocks the session unexpectedly, or configuration-file hooks are not enabled. Provides configuration sources, the hooks.json schema, how to inspect hooks in the client, and a step-by-step localization and repair workflow.From its SKILL.md
npx -y skills add phoben/agent-skills --skill diagnosing-hooksAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.3 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it
Diagnosing Hook Configuration
Goal: reduce any hook problem to a single concrete fix.
Note on trust: plugin hooks execute regardless of the marketplace they came from — third-party plugin hooks run just like built-in ones. Any "diagnostic-only until trusted" wording you may see for marketplace hooks is stale; a plugin's detail view marks each hook as runnable, and that is now true for all hooks.
1. Configuration sources and merging
- Configuration-file hooks: the top-level
hookskey in~/.zcode/cli/config.json(or the workspace<repo>/.zcode/config.json/zcode.json), shaped as{ enabled?, timeoutMs?, maxOutputBytes?, events: { <Event>: [ { matcher?, hooks: [...] } ] } }. These are disabled by default — configuration-file hooks must sethooks.enabled: trueto run. - Plugin hooks: each plugin's
hooks/hooks.json(or its manifesthooksfield). Plugin matchers are appended after configuration matchers. When any plugin contributes a hook, the hook runner is enabled automatically. - Non-plugin (user or workspace) configuration hooks have no trust gate; with
enabled: truethey run unconditionally.
2. hooks.json schema
{ "hooks": { "<Event>": [ { "matcher": "...", "hooks": [ { "type": "command"|"process", ... } ] } ] } }
(A plugin file uses the outer hooks wrapper; the configuration file uses hooks.events.<Event>. The inner array is the same.)
- Event names (exactly seven):
SessionStart,UserPromptSubmit,PreToolUse,PermissionRequest,PostToolUse,PostToolUseFailure,Stop. Any other name is an unsupported event. (Events such asNotification,SubagentStop, andPreCompactare not supported.) - The matcher is a case-sensitive regular expression, tested against the event's match value:
SessionStart→ one ofstartup,resume,clear,compact- Tool events (
PreToolUse,PostToolUse,PermissionRequest,PostToolUseFailure) → the tool name (Bash,Read,Write,Edit,Agent, …), with aliasesTask↔AgentandWrite/Edit←ApplyPatch UserPromptSubmit→ the prompt text;Stop→ the response preview- An omitted matcher matches everything; an invalid regular expression never matches (silently)
type: "command":command(a shell string); optionalshell,timeout(in seconds),timeoutMs(in milliseconds, takes precedence), andstatusMessage. Note thatasynccurrently has no runtime effect.type: "process":command(an executable) plusargs[](an argument vector run without a shell, the most portable choice),timeoutMs(in milliseconds), andstatusMessage.- Timeout resolution:
timeoutMs→timeout × 1000→ the configuration'stimeoutMs→ a default of 60000 ms. - Template variables (expanded in the command and each argument, and also injected as environment variables):
${CLAUDE_PROJECT_DIR}/${ZCODE_PROJECT_DIR},${CLAUDE_SESSION_ID}; and, for plugin hooks only,${CLAUDE_PLUGIN_ROOT}/${ZCODE_PLUGIN_ROOT}and the plugin data directory. Note that a skill-directory variable is not valid in a hook and raises an error. - Hook output: standard output is parsed as JSON (a strict schema — any extra key fails validation), or you may use exit codes:
0passes,2blocks (a deny forPreToolUse/PermissionRequest), and any other non-zero raises an error.additionalContextis injected into the conversation;PreToolUsemay return a permission decision ofallow/ask/deny;Stopmay request continuation (up to three times).
3. How to inspect hooks
- In the client: Settings → Plugin Management → open a plugin's detail view to see the hooks it registers and whether each is runnable.
- As an agent: read the
hooks/hooks.json(or the manifesthooksfield) for a plugin, and thehooksblock of~/.zcode/cli/config.json/ the workspace config for configuration hooks. - Execution (fired, timed out, blocked) is recorded in the ZCode log, with the hook's source, matcher, outcome, duration, and a preview of its error stream — enough to distinguish a timeout from a failure from a block.
4. Common pitfalls (symptom → cause → fix)
- Configuration-file hooks do not run — you added
hooks.events.*but nothing fires. They are disabled by default and are enabled automatically only when a plugin hook is present. → Set"hooks": { "enabled": true, ... }in the configuration. - Wrong event name — the hook never triggers. → Use exactly one of the seven supported events.
- Matcher does not match (tool name, case, or regex) — it is registered but never fires for the tool you expect. The matcher is a case-sensitive regular expression;
"bash"will not matchBash, and an invalid expression never matches. → Use the exact tool name or a correct expression (for example"Edit|Write"), or omit the matcher to match all. Remember the aliasesTask→AgentandWrite/Edit→ApplyPatch. - Script is not executable —
permission denied, with a failed outcome. The script was installed without the executable bit. → Runchmod +xon it, or invoke it through an interpreter, e.g.{"type":"command","command":"bash \"${CLAUDE_PLUGIN_ROOT}/hooks/x.sh\""}, so the executable bit is irrelevant. - Template variable not expanded — a literal
${...}or an empty path. Only recognized variables are expanded; a skill-directory variable raises an error inside a hook, and${CLAUDE_PLUGIN_ROOT}is available only for plugin hooks. → Use only supported variables, and${CLAUDE_PLUGIN_ROOT}for plugin-relative paths. - Timeout unit mistaken — the hook is killed with a timed-out outcome.
command'stimeoutis in seconds;process'stimeoutMsis in milliseconds.timeout: 500means 500 seconds;timeoutMs: 5means 5 milliseconds. → Use"timeout": <seconds>for a command hook and"timeoutMs": <milliseconds>for a process hook. - Command and process fields mixed — the hook is dropped. A
processhook accepts onlycommand,args, andtimeoutMs; acommandhook acceptscommand,shell,timeout, andtimeoutMs. → Match the fields to thetype. - JSON output fails validation — the hook ran but its effect was discarded and the run marked failed. The output was not valid JSON, contained an extra key (the schema is strict), or its event-specific output named the wrong event. → Emit only the recognized keys with the correct event name, or emit nothing (empty output is fine) and rely on exit codes.
- Assuming
asyncruns in the background — you setasync: truebut the session still waits. Theasyncfield has no runtime effect and hooks always run inline. → Do not rely onasync; for background work, have the script daemonize itself. - Cross-platform failure — it works on one operating system but not another. A
commandhook runs through a shell, so POSIX syntax fails on Windows. → Prefer aprocesshook (an argument vector, no shell), or ship a polyglot wrapper script and keep hook scripts extensionless. - A hook blocks the session unexpectedly — a tool is denied or the run halts. The hook returned a block, exited with code 2, or returned a
denydecision. → Inspect the block's reason in the log; fix the script's exit code — return 0 to pass and reserve 2 for a deliberate block. - Believing third-party hooks are "diagnostic only" — a third-party plugin hook did not run and you suspect a trust gate. That is not the cause: all plugin hooks are runnable. → Diagnose via pitfalls 2, 3, 4, and 8.
5. Localization workflow (in order)
- Is a runner active? Confirm that either
hooks.enabled: trueis set in the configuration or at least one plugin contributes a hook; otherwise no runner exists and every hook is skipped. - Enumerate what is registered. In Settings → Plugin Management, open the plugin's detail view and confirm the hook you expect is present and runnable, and that its plugin is enabled. For configuration hooks, read the
hooksblock directly. - Event name and matcher. Check against the seven events; confirm the match value and the case-sensitive expression; test by omitting the matcher (which matches everything).
- Executable and interpreter. Confirm the script has its executable bit, or invoke it explicitly via
bash/node. - Run it by hand. Feed a sample hook input to the script and inspect the exit code and output —
0plus valid JSON is healthy,2is a deliberate block, any other non-zero is a failure. - Observe live. Trigger the event and read the hook run records in the log (outcome, duration, error-stream preview) to distinguish a timeout from a failure from a block.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.