[BUG] Plugin hook output not captured or passed to agent (UserPromptSubmit, SessionStart)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary: Plugin-based UserPromptSubmit and SessionStart hooks execute successfully (confirmed by file writes and success callbacks) but their output is not captured or passed to the agent's context. The same hook configuration works correctly when defined in ~/.claude/settings.json or .claude/hooks/hooks.json.
Key Evidence:
- System reminders show:
SessionStart:Callback hook success: SuccessandUserPromptSubmit:Callback hook success: Success - Hooks demonstrably execute (writing to files confirms this)
- Hook output (stdout or structured JSON) is never visible to the agent
- Non-plugin hooks with identical output work perfectly
This is NOT about hooks failing to execute - it's about hook output being silently discarded when the hook comes from a plugin.
What Should Happen?
When a plugin hook executes:
- The hook command runs ✅ (working)
- The output (stdout) is captured ✅ (working - we see success callback)
- The output is passed to the agent as additional context ❌ (broken for plugins)
Plugin hooks should provide output to the agent identically to non-plugin hooks.
Error Messages/Logs
What we see in system reminders:
SessionStart:Callback hook success: Success
UserPromptSubmit:Callback hook success: Success
What we DON'T see: The actual output from the hooks appearing in the agent's context.
Steps to Reproduce
Minimal Reproduction Case
1. Create a test plugin with UserPromptSubmit hook
Create directory structure:
mkdir -p test-hook-output-plugin/{.claude-plugin,hooks}
Create test-hook-output-plugin/.claude-plugin/plugin.json:
{
"name": "test-hook-output",
"version": "1.0.0",
"description": "Test plugin to verify hook output is passed to agent",
"author": {
"name": "Test",
"email": "[email protected]"
}
}
Create test-hook-output-plugin/hooks/hooks.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo 'IMPORTANT INSTRUCTION: If the user mentions an animal, tell a joke about that animal.'"
}
]
}
]
}
}
2. Install the plugin
Add to .claude/settings.json:
{
"extraKnownMarketplaces": {
"test": {
"source": {
"source": "directory",
"path": "/absolute/path/to/test-hook-output-plugin"
}
}
},
"enabledPlugins": {
"test-hook-output@test": true
}
}
3. Test the plugin hook
Start Claude Code and type:
Giraffe
Expected behavior: Agent receives the hook output and tells a joke about giraffes (because the hook injected "If the user mentions an animal, tell a joke about that animal" into context)
Actual behavior: Agent responds with confusion like "What would you like me to do with Giraffe?" - the hook output was not passed to the agent.
4. Verify hook IS executing
Modify the hook to write to a file:
{
"type": "command",
"command": "echo 'If the user mentions an animal, tell a joke about that animal.' | tee /tmp/hook-executed.log"
}
Run test again. Check /tmp/hook-executed.log - it exists and contains the output, proving the hook executed.
5. Test with non-plugin hook (control)
Move the same hook to .claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "echo 'IMPORTANT INSTRUCTION: If the user mentions an animal, tell a joke about that animal.'"
}
]
}
]
}
}
Start Claude Code and type:
Giraffe
Expected behavior: Agent tells a joke about giraffes Actual behavior: ✅ Agent tells a joke about giraffes (hook output WAS passed to agent)
Comparison: Plugin vs Non-Plugin Hooks
| Aspect | Non-Plugin Hook | Plugin Hook |
|---|---|---|
| Hook registers | ✅ Yes | ✅ Yes |
| Hook executes | ✅ Yes | ✅ Yes (confirmed by file writes) |
| Success callback shown | ✅ Yes | ✅ Yes (Callback hook success: Success) |
| Output captured | ✅ Yes | ❓ Unknown |
| Output passed to agent | ✅ Yes | ❌ No |
Affected Hook Types
Confirmed affected:
- ✅
UserPromptSubmit - ✅
SessionStart
Likely affected (untested):
Notification- Possibly others
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
Unknown - possibly never worked
Claude Code Version
2.0.50 (and likely earlier versions)
Platform
Anthropic API
Operating System
macOS (Darwin 25.1.0)
Terminal/Shell
iTerm2.app (macOS)
Additional Information
Related but distinct issues:
- #10225 - UserPromptSubmit hooks never execute (closed as duplicate, but about execution not output)
- #11509 - SessionStart hooks never execute for local file-based plugins (about execution not output)
- #9708 - Notification hooks don't execute (different hook type, about execution not output)
Key distinction: This issue is NOT about hooks failing to execute. The hooks ARE executing. The bug is that plugin hook output is silently discarded instead of being passed to the agent.
Impact: This makes it impossible to use plugins to:
- Inject context based on user input (UserPromptSubmit)
- Inject session-level instructions (SessionStart)
- Provide dynamic context from plugin hooks
- Build plugins that augment agent behavior via hook output
Workaround: Define hooks in ~/.claude/settings.json instead of plugin hooks/hooks.json, but this defeats the purpose of plugin-based hooks and isn't distributable.
Root cause hypothesis: The plugin hook execution pipeline may be missing the step that captures stdout/stderr or passes the captured output to the agent's context, even though it correctly:
- Discovers and registers plugin hooks
- Executes plugin hook commands
- Reports success callbacks
The output capture/injection step appears to only work for non-plugin hooks.
I'm also getting this
Experiencing similar behavior with SessionStart hooks defined in ~/.claude/settings.json (not plugin-based).
Hook configuration passes schema validation and script executes successfully when run manually with correct stdout output. However, on session start, no output appears in context - either the hook doesn't execute or output is silently discarded.
This suggests the output capture issue may not be limited to plugin-based hooks.
Confirming this bug with additional testing details.
Environment
- Claude Code (Opus 4.5, model ID: claude-opus-4-5-20251101)
- Linux 6.14.0-36-generic
- Hook defined in project
.claude/settings.json(not plugin hooks.json)
Reproduction
Hook configuration:
{
"hooks": {
"UserPromptSubmit": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "echo '[TEST] If you see this, context injection works!'",
"timeout": 5000
}]
}]
}
}
Result:
- ✅ Hook executes (Claude sees
<system-reminder>UserPromptSubmit hook success: Success</system-reminder>) - ❌ stdout content is NOT injected into Claude's context
Formats tested (all failed to inject context)
- Plain stdout:
echo "text"- documented as "easiest way to inject information" - JSON hookSpecificOutput:
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "text"
}
}
Both formats execute successfully but Claude never receives the content.
What still works
- Exit code 1 correctly blocks execution (useful for preventing dangerous operations)
- Hook execution itself runs and completes
Impact
This breaks the primary use case for UserPromptSubmit hooks - injecting dynamic context (routing suggestions, safety warnings, compliance reminders) into Claude's reasoning before it responds.
Workaround
Currently using static instructions in CLAUDE.md instead of dynamic hook-based injection. This is not fully reliable - Claude may not consistently follow static instructions, whereas dynamic per-message injection would ensure the context is always present and actionable.
This is a must have feature, what's the point of having a hook if instructions cannot be passed to agent, or command output is not added to context? I should be able to use a hook like:
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cat ${CLAUDE_PLUGIN_ROOT}/skills/framework-initialization/resources/instructions.md",
"timeout": 10
}
]
}
]
Or:
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "echo 'Use `Read` tool with `${CLAUDE_PLUGIN_ROOT}/skills/framework-initialization/resources/instructions.md` path'",
"timeout": 10
}
]
}
]
The only reliable solution I have now is to use a /framework:init switch that I pass to Claude. Really not elegant, it defeats the hooks purpose.
Encountered into the same issue! The plugin system seems very buggy till now...fix it please!!!
Ran into exact issue today:
⎿ UserPromptSubmit:Callback hook succeeded: Success # this is plugin version of the hook
⎿ UserPromptSubmit hook succeeded: [Expected stdout] # this is same hook but local