[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.
24 Comments
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/settings.json(not plugin hooks.json)Reproduction
Hook configuration:
Result:
<system-reminder>UserPromptSubmit hook success: Success</system-reminder>)Formats tested (all failed to inject context)
echo "text"- documented as "easiest way to inject information"Both formats execute successfully but Claude never receives the content.
What still works
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:
Or:
The only reliable solution I have now is to use a
/framework:initswitch 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:
Also ran into the same issue. Prevents me from using the plugin system which is painful.
I have the same issue with PreCompact hook.
.claude/settings.jsonworks, stdout injected as custom compact instructionsSame hook, different location - different behaviour.
I'm experiencing the same issue with UserPromptSubmit hooks not executing in Cursor. Here's my detailed findings:
Problem Summary
settings.jsondo not execute in CursorEvidence & Test Results
Hook Configuration
Test Results
Claude CLI:
Cursor IDE:
Debugging Attempts
capture-all-events.ts:promptfieldImpact
Use Case
I need real-time conversation capture for:
Additional Context
I've documented this issue in detail, including:
The core issue is that UserPromptSubmit hooks are configured but never execute in Cursor, preventing real-time conversation capture.
Related Issues:
---
We are also facing this issue. The SessionStart hook executes successfully, but Claude never sees it. Are there any plans to fix this?
Coming from #16326 (marked as duplicate), I want to highlight a related user experience issue that stems from this bug:
The Cognitive Disconnect Problem
When hooks execute but their output isn't captured, Claude not only lacks context—it actively claims ignorance of hooks that are observably running.
Example conversation:
This creates a trust problem where Claude contradicts observable system behavior.
Root Cause (Same as This Issue)
The problem is identical to what's described here: hooks execute successfully at the system level, but their output never reaches Claude's context. In my case:
~/.claude/settings.json(global, not plugin-based)Suggested Enhancement
Beyond fixing the output capture bug, consider session initialization context injection:
At session start, Claude should acknowledge configured hooks even before they execute:
This would:
The output capture fix is critical, but initialization context would make Claude's behavior consistent with its actual configuration—even if specific hook output later fails to capture.
---
Environment from #16326:
~/.claude/settings.json)Wow, this is a pretty big bug to remain unresolved so long. On a pretty central feature.
+1, also running into this in vscode, both via terminal and claude code plugin.
this seems to be fixed on v2.1.3
Same here. Using 2.1.5.
+1, can't use my auto-injecting memory system due to this issue.
Still experiencing this issue. Created a minimal reproduction case that confirms plugin UserPromptSubmit hooks execute but output is not captured.
Test:
console.log('🔔 TEST HOOK EXECUTED');However:
Moving the identical hook configuration from plugin
hooks/hooks.jsonto~/.claude/settings.jsonworks perfectly - Claude sees the output in context.Diagnosis:
The hook executes successfully (can verify with file write) but the stdout capture mechanism appears broken for plugin-based UserPromptSubmit hooks specifically.
Environment:
~/.claude/plugins/Workaround:
Use
~/.claude/settings.jsoninstead of plugin hooks.json, though this requires manual user configuration.Full reproduction: https://github.com/Lansweeper/wiz-agents
I'm hitting this same issue with non-plugin hooks defined in
~/.claude/settings.json.Setup:
UserPromptSubmithook in~/.claude/settings.json(not a plugin)hookSpecificOutput.additionalContextper docsBehavior:
additionalContextcontent never appears in Claude's contextWhat I tried:
hookSpecificOutput.additionalContextandhookEventName→ not injectedThe hook runs, the JSON is valid, but the context injection step seems to be skipped entirely. This blocks a key use case: injecting dynamic context (like notifications from other systems) into the conversation.
Would love to see this fixed - it's a significant limitation for automation workflows.
Native hook workaround +
hookEventNameis silently requiredWe spent ~60 min debugging this on v2.1.42 and ran 5 format probes on a native
UserPromptSubmithook (in~/.claude/settings.json, not a plugin). Sharing results because @greghughespdx's comment (Jan 31) hit the same native-hook failure we did.Probe results (native UserPromptSubmit hook, v2.1.42)
| # | Output format | Result |
|---|--------------|--------|
| 1 |
{"systemMessage":"..."}| ❌ Content dropped, shows "hook success: Success" only || 2 |
{"hookSpecificOutput":{"additionalContext":"..."}}| ❌ Silently dropped || 3 | stderr (with exit 0) | ❌ Not injected |
| 4 | Plain text stdout | ✅ Injected as
"hook success: [text]"|| A |
{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"..."}}| ✅ Injected as"hook additional context: [text]"|Key finding:
hookEventNameis silently requiredProbe 2 and Probe A are identical except Probe A includes
"hookEventName": "UserPromptSubmit"insidehookSpecificOutput. Without it, the entire JSON output is silently dropped — no error, no warning, just "Success."The docs show
hookEventNamein the example JSON but never call it out as required. This likely explains @greghughespdx's report — native hooks DO work, but only with the exact documented format includinghookEventName.What this means for this issue
The plugin hook bug (original report) is still real — plugin hooks' output is dropped regardless of format. But some reports in this thread about "native hooks also broken" may actually be the missing
hookEventNameproblem, not the plugin bug.Working native hook example
Hope this helps others hitting the native-hook side of this issue. The plugin bug is separate and still needs fixing.
Real-world impact: forced to build file-based workaround
We're building a hook-based integration that needs to inject contextual information into Claude's conversation via
UserPromptSubmithooks. The documentedadditionalContextfield in stdout would be the clean solution, but any stdout output triggers"UserPromptSubmit hook error"— even valid JSON with exit code 0.What we tried (all failed)
| Approach | Result |
|----------|--------|
|
echo '{"hookSpecificOutput":{"additionalContext":"..."}}'+ exit 0 | ❌ Hook error ||
printf '{"decision":"continue"}'+ exit 0 | ❌ Hook error || Any non-empty stdout + exit 0 | ❌ Hook error |
| Empty stdout
echo '{}'+ exit 0 | ✅ Works (but no context injected) |Additional finding: stderr sensitivity
CC CLI also treats any stderr output as a hook error, even with exit code 0. Tools like
npx,jq, and other CLI utilities often write to stderr (progress bars, warnings, deprecation notices). We had to addexec 2>>debug.logat the top of every hook script to redirect all stderr to a file.Workaround we had to build
Since hooks can't inject context via stdout, we built a file-based workaround:
CLAUDE.mdinstruction telling Claude to read the file at the start of each responseRead 1 file) and deletes it (user seesBash rm)This works but the UX is poor — users see tool call noise instead of clean context injection. The
additionalContextfield would eliminate all of this.Environment
.claude/settings.json(project-level)Priority request
This is a blocking issue for anyone building hook-based integrations that need to enrich Claude's context. The documented API (
additionalContextin stdout) simply doesn't work forUserPromptSubmit. Fixing this would eliminate the need for file-based workarounds and significantly improve the developer experience for hook authors.Potential Resolution: Missing
hookEventNamePropertyThe official hooks documentation has been updated (as of Feb 2026) to explicitly state that
hookEventNameis required in allhookSpecificOutputresponses:Issue Analysis
Looking at the reproduction examples in this issue, the hook commands output plain text:
Without
hookEventName, hook output is silently dropped regardless of whether it's from a plugin or native hook.Suggested Test
Please test your plugin hooks with properly formatted JSON output including
hookEventName:For UserPromptSubmit hook:
For SessionStart hook:
Hypothesis
The difference you observed between plugin hooks and native hooks may have been:
hookEventName→ output silently droppedhookEventName(or used plain text stdout format) → worked correctlyIf adding
hookEventNameresolves the issue, this would indicate plugin hooks work correctly and the problem was the undocumented format requirement.References
Can you test with the corrected format and report back? This may resolve the plugin hook output issue entirely.
Confirming @jpicklyk's findings on v2.1.45 (latest). Our native hooks with
hookEventNamework correctly for:additionalContextinjected as "hook additional context: [text]" ✅additionalContextinjected ✅All use
{"hookSpecificOutput":{"hookEventName":"<event>","additionalContext":"..."}}in~/.claude/settings.json(non-plugin).Gotcha for PostToolUse hook authors:
tool_responsein the hook's stdin JSON is a structured object (e.g.,{"numFiles":0,"filenames":[]}for Grep), NOT the display string "No files found". If your hook pattern-matches against the string, it will silently fail to match. Usejq '.tool_response.numFiles'instead.Plugin hooks remain broken for output injection — same hooks moved to a plugin's
hooks/hooks.jsonproduce "Success" with no context injected.This is a known gap — plugin-delivered hooks execute but their output isn't injected into the conversation context the same way user-level hooks are.
Workaround 1 — Move hooks from plugin to user settings:
Copy the hook configuration from your plugin to
~/.claude/settings.jsondirectly:Hooks in user-level settings have their output properly captured and injected into context.
Workaround 2 — Write output to a file, read via CLAUDE.md:
If you want to keep hooks in a plugin, have them write to a file instead:
Then in CLAUDE.md:
Workaround 3 — Use project-level settings instead of plugin:
Project-level hooks (not via plugin) should have their output captured properly.
The root issue is that plugin-delivered hooks go through a different execution path than user/project-level hooks, and the output capture isn't wired up for the plugin path.
Adding data from our investigation (filed separately as #45438 before finding these dupes):
This regressed in v2.1.97 (released 2026-04-08). Plugin SessionStart additionalContext was working correctly on v2.1.96 earlier today. Same plugin, same hooks.json, same machine.
Confirmed: hook executes (side effects work), stdout JSON with hookSpecificOutput.additionalContext is silently discarded in interactive terminal mode. --print mode may still work. Stop hooks in the same plugin/hooks.json work correctly. Tested across 5 agents in different project directories.