anthropics / anthropics/claude-plugins-official

docs(hook-development): systemMessage documented as reaching Claude; additionalContext missing entirely

未關閉
#4,591 0 則留言 1 個 reaction 已指派 0 人 在 GitHub 檢視
主要語言
Python
星號
36.3k
分支
4.1k
平均合併
2 天 14 小時
30 天內合併 PR
539

描述

`plugins/plugin-dev/skills/hook-development/SKILL.md` teaches `systemMessage` as the way a hook talks to Claude, never mentions `hookSpecificOutput.additionalContext` in its 712 lines, and its own runnable examples demonstrate the resulting bug. The failure is silent.

Filing as an issue rather than a PR: I opened #4590 with this fix and it was auto-closed by `close-external-prs.yml`. Following the precedent of #4328, which reports a doc bug in this same file the same way. Ready-to-apply diff at the bottom.

## The inaccurate lines

| Line | Current text |
|------|--------------|
| 151 | `"systemMessage": "Explanation for Claude"` |
| 179 | `- systemMessage included in context` |
| 207 | `"systemMessage": "Additional context"` (Stop) |
| 286 | `"systemMessage": "Message for Claude"` |
| 292 | ``- `systemMessage`: Message shown to Claude`` |

`additionalContext` appears **0 times** in `hook-development/` — neither `SKILL.md` nor `references/advanced.md`.

## What the docs say

From https://docs.claude.com/en/docs/claude-code/hooks:

- `systemMessage` — "Warning message shown to the user", and "A `systemMessage` field is shown to you, not to Claude."
- `hookSpecificOutput` — "It requires a `hookEventName` field set to the event name."
- `permissionDecisionReason` — "For `allow` and `ask`, shown to the user but not Claude. For `deny`, shown to Claude. For `defer`, ignored." Listed for `PreToolUse` only.
- `stopReason` — "Message shown to the user when `continue` is `false`. Not shown to Claude." Distinct from `reason`.
- `reason` with `decision: "block"` — "Required when `decision` is `\"block\"`. Tells Claude why it should continue."
- `additionalContext` is supported on `SessionStart`, `Setup`, `SubagentStart`, `UserPromptSubmit`, `UserPromptExpansion`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`, `Stop`, `SubagentStop`.

## The skill's own examples demonstrate the bug

`examples/validate-bash.sh:26,32,38` and `examples/validate-write.sh:21,27,33` all emit this shape:

```json
{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Dangerous command detected: rm -rf"}
```

`permissionDecision: "deny"` with no `permissionDecisionReason`, explanation in `systemMessage` — Claude is told the tool was refused and never told why. These are the canonical examples the skill points readers at, so the wrong pattern is taught by demonstration as well as by prose.

## Why it matters

A hook written from this skill renders a message on screen and changes nothing about the agent's behavior, with no error to notice. The same root cause is already reported downstream against `hookify`, whose `warn` path emits `systemMessage` alone and whose `block` path emits `permissionDecision: "deny"` with no `permissionDecisionReason`: #282, #292, #317, #582, #1738 — open since January. Fixing the skill is upstream of all of them, since it is what teaches the pattern.

The file's content has not been revised since it was created on 2025-11-20 (only a filename-case rename since).

## Proposed change

- Correct the five inaccurate `systemMessage` descriptions.
- Add `hookEventName`, `permissionDecisionReason`, `additionalContext` to the PreToolUse output example; correct the Stop example.
- Add a **Reaching Claude vs. Reaching the User** section distinguishing the five fields that are easy to confuse: `additionalContext`, `permissionDecisionReason`, `systemMessage`, `stopReason`, and `reason`.
- List `stopReason` in **Standard Output (All Hooks)**, where it was missing.
- Fix both example scripts: all six payloads now carry `hookEventName` and `permissionDecisionReason`. JSON parses; `bash -n` passes.

Matches existing file conventions — Title Case heading, padded table separator, unwrapped prose.

## Deliberately not changed

- `SKILL.md:480,494,500` (Security Best Practices) use the legacy top-level `{"decision": "deny", "reason": "..."}` shape for PreToolUse rather than `hookSpecificOutput`. Same in `references/patterns.md:328`, `references/migration.md:112,118,168`, `references/advanced.md:332,372`. I could not confirm from the docs whether that shape is still supported for PreToolUse, so I left it rather than guess — worth a maintainer's call as a follow-up.
- The doc hostname. `docs.claude.com/...` 301-redirects to `code.claude.com/...`, but `docs.claude.com` is used across 7 files in `plugins/`; switching one file would make it the odd one out. Repo-wide change, not a rider here.

Documentation and examples only, +42/-12, three files. No manifest, no hook registration, no runtime behavior.

Branch: https://github.com/Antheurus/claude-plugins-official/tree/docs/hook-development-additional-context

Full diff

```diff
diff --git a/plugins/plugin-dev/skills/hook-development/SKILL.md b/plugins/plugin-dev/skills/hook-development/SKILL.md
index 6317441..31b163f 100644
--- a/plugins/plugin-dev/skills/hook-development/SKILL.md
+++ b/plugins/plugin-dev/skills/hook-development/SKILL.md
@@ -145,13 +145,18 @@ Execute before any tool runs. Use to approve, deny, or modify tool calls.
```json
{
"hookSpecificOutput": {
+ "hookEventName": "PreToolUse",
"permissionDecision": "allow|deny|ask",
+ "permissionDecisionReason": "Why - shown to Claude on deny, shown to the user on allow/ask",
+ "additionalContext": "Extra context added to Claude's context window",
"updatedInput": {"field": "modified_value"}
},
- "systemMessage": "Explanation for Claude"
+ "systemMessage": "Warning shown to the user in the terminal - Claude does not see this"
}
```

+When denying a tool, put the explanation in `permissionDecisionReason`. Claude is told the tool was blocked either way, but only that field tells it why.
+
### PostToolUse

Execute after tool completes. Use to react to results, provide feedback, or log.
@@ -176,7 +181,8 @@ Execute after tool completes. Use to react to results, provide feedback, or log.
**Output behavior:**
- Exit 0: stdout shown in transcript
- Exit 2: stderr fed back to Claude
-- systemMessage included in context
+- `hookSpecificOutput.additionalContext`: added to Claude's context window
+- `systemMessage`: shown to the user only - it does not reach Claude

### Stop

@@ -203,11 +209,13 @@ Execute when main agent considers stopping. Use to validate completeness.
```json
{
"decision": "approve|block",
- "reason": "Explanation",
- "systemMessage": "Additional context"
+ "reason": "Required when decision is block - tells Claude why it should continue",
+ "systemMessage": "Warning shown to the user"
}
```

+`reason` reaches Claude only in the `block` case. For feedback that should not block, Stop also accepts `hookSpecificOutput.additionalContext`.
+
### SubagentStop

Execute when subagent considers stopping. Use to ensure subagent completed its task.
@@ -283,13 +291,35 @@ Execute when Claude sends notifications. Use to react to user notifications.
{
"continue": true,
"suppressOutput": false,
- "systemMessage": "Message for Claude"
+ "systemMessage": "Warning shown to the user in the terminal"
}
```

- `continue`: If false, halt processing (default true)
- `suppressOutput`: Hide output from transcript (default false)
-- `systemMessage`: Message shown to Claude
+- `stopReason`: Message shown to the user when `continue` is false. Not shown to Claude
+- `systemMessage`: Warning shown to the **user**. It is not added to Claude's context — use `hookSpecificOutput.additionalContext` to send text to Claude
+
+### Reaching Claude vs. Reaching the User
+
+These go to different places. Mixing them up produces a hook that looks like it works — a message appears on screen — while Claude never receives it and never acts on it. A hook whose purpose is to steer Claude's next action must use `additionalContext`, or `permissionDecisionReason` when denying a tool.
+
+| Field | Delivered to | Available on |
+|-------|--------------|--------------|
+| `hookSpecificOutput.additionalContext` | Claude's context window | `SessionStart`, `Setup`, `SubagentStart`, `UserPromptSubmit`, `UserPromptExpansion`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PostToolBatch`, `Stop`, `SubagentStop` |
+| `hookSpecificOutput.permissionDecisionReason` | Claude on `deny`; the user on `allow` and `ask` | `PreToolUse` |
+| `reason` (with `decision: "block"`) | Claude — why it should continue | `Stop`, `SubagentStop` |
+| `systemMessage` | The user, in the terminal | All hooks |
+| `stopReason` | The user when `continue` is false — **not** Claude | All hooks |
+
+```json
+{
+ "hookSpecificOutput": {
+ "hookEventName": "PostToolUse",
+ "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
+ }
+}
+```

### Exit Codes

diff --git a/plugins/plugin-dev/skills/hook-development/examples/validate-bash.sh b/plugins/plugin-dev/skills/hook-development/examples/validate-bash.sh
index e364324..e35e7ca 100755
--- a/plugins/plugin-dev/skills/hook-development/examples/validate-bash.sh
+++ b/plugins/plugin-dev/skills/hook-development/examples/validate-bash.sh
@@ -23,19 +23,19 @@ fi

# Check for destructive operations
if [[ "$command" == *"rm -rf"* ]] || [[ "$command" == *"rm -fr"* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Dangerous command detected: rm -rf"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Dangerous command detected: rm -rf"}}' >&2
exit 2
fi

# Check for other dangerous commands
if [[ "$command" == *"dd if="* ]] || [[ "$command" == *"mkfs"* ]] || [[ "$command" == *"> /dev/"* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Dangerous system operation detected"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Dangerous system operation detected"}}' >&2
exit 2
fi

# Check for privilege escalation
if [[ "$command" == sudo* ]] || [[ "$command" == su* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "ask"}, "systemMessage": "Command requires elevated privileges"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "ask", "permissionDecisionReason": "Command requires elevated privileges"}}' >&2
exit 2
fi

diff --git a/plugins/plugin-dev/skills/hook-development/examples/validate-write.sh b/plugins/plugin-dev/skills/hook-development/examples/validate-write.sh
index e665193..4319ec8 100755
--- a/plugins/plugin-dev/skills/hook-development/examples/validate-write.sh
+++ b/plugins/plugin-dev/skills/hook-development/examples/validate-write.sh
@@ -18,19 +18,19 @@ fi

# Check for path traversal
if [[ "$file_path" == *".."* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Path traversal detected in: '"$file_path"'"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Path traversal detected in: '"$file_path"'"}}' >&2
exit 2
fi

# Check for system directories
if [[ "$file_path" == /etc/* ]] || [[ "$file_path" == /sys/* ]] || [[ "$file_path" == /usr/* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Cannot write to system directory: '"$file_path"'"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Cannot write to system directory: '"$file_path"'"}}' >&2
exit 2
fi

# Check for sensitive files
if [[ "$file_path" == *.env ]] || [[ "$file_path" == *secret* ]] || [[ "$file_path" == *credentials* ]]; then
- echo '{"hookSpecificOutput": {"permissionDecision": "ask"}, "systemMessage": "Writing to potentially sensitive file: '"$file_path"'"}' >&2
+ echo '{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "ask", "permissionDecisionReason": "Writing to potentially sensitive file: '"$file_path"'"}}' >&2
exit 2
fi

```

貢獻指南

這個儲存庫沒有索引到貢獻指南

研究方向

Read plugins/plugin-dev/skills/hook-development/SKILL.md and the examples/validate-bash.sh and examples/validate-write.sh scripts, then compare their output fields with the linked Claude hooks documentation. Done means the documented field destinations and supported events are accurate, both examples use the intended fields, their JSON parses, and bash -n passes for both scripts.

由索引模型根據 Issue 內容生成。

評估

技術堆疊
shell
領域
documentation
Issue 類型
文件
難度
3/5
預估耗時
1-2 天
活躍度
冷清
描述清晰度
描述清楚
新手友好度
45/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。