How to fix Claude Code SessionStart hooks firing without context

Plugin SessionStart hooks fire but Claude does not see additionalContext. Move the hook to ~/.claude/settings.json. Plus four similar bugs that need different fixes.

How to fix Claude Code SessionStart hooks firing without context

TL;DR: If a Claude Code plugin's SessionStart hook fires successfully but Claude never sees the additionalContext (bug #16538), move the hook config out of the plugin's hooks.json and into ~/.claude/settings.json.

The user-config path injects additionalContext correctly; the plugin loading path silently drops it. Issue #16538 tracks the bug.

That is the entire fix in one sentence. The rest of this post is about telling this bug apart from four similar-looking ones, because most of the workarounds floating around target a different failure.

Two horizontal flow paths comparing how Claude Code processes SessionStart hook output. Top path: plugin/hooks.json to plugin loader to hook process to stdout JSON, marked DROPPED in red, so Claude only sees Callback hook success. Bottom path: ~/.claude/settings.json to user-settings loader to hook process to stdout JSON, marked PARSED in green, so Claude receives the additionalContext text. Same hook script, same JSON, same exit code, only the loader differs.
Same hook script in both paths; the plugin loader drops the stdout JSON before it reaches Claude. Source: issue #16538.

What "fires but no context" actually looks like

You wrote a plugin with a hook block like this:

{
  "hooks": {
    "SessionStart": [{
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
      }]
    }]
  }
}

The script runs and prints valid JSON to stdout:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Project uses Postgres 17. Service URL is https://internal-svc."
  }
}

You start a new Claude Code session. The transcript shows:

SessionStart:Callback hook success: Success

And nothing else. Claude does not know about Postgres 17 or the service URL. If you ask "what database does this project use?" the answer is a guess, not the value you injected.

The fix: move the same config to your user settings

Open (or create) ~/.claude/settings.json on macOS or Linux, or %USERPROFILE%\.claude\settings.json on Windows. Paste the same hook block, but drop the ${CLAUDE_PLUGIN_ROOT} reference and use an absolute path to your script:

{
  "hooks": {
    "SessionStart": [{
      "hooks": [{
        "type": "command",
        "command": "/Users/you/scripts/session-start.sh"
      }]
    }]
  }
}

Restart Claude Code and start a new session. The transcript should now show your hook output as injected context, not the bare success line. The same script, the same JSON shape, the same exit code. The only thing that changed is which file Claude Code read the hook config from.

If you publish the plugin and want users on different machines to install the hook automatically, the answer right now is that they have to copy the block into their own user settings file. Plugin distribution of SessionStart hooks is broken; there is no clean workaround that keeps the hook inside the plugin package.

Four similar-looking bugs that need different fixes

Hook issues in the Claude Code repo all read the same in headlines, but four of them are distinct from the "no additionalContext" case above. Knowing which one you have saves an hour of trying the wrong workaround.

SymptomIssueFix or status
Hook fires, JSON is correct, transcript shows only "Callback hook success" with no context#16538Move config to user settings (this post)
Hook does not fire at all on a brand-new conversation, but fires after /clear or /compact#10373Same loading-path bug; user-settings move usually helps but not always
Hook never executes for plugins installed from a local file-based marketplace#11509Distinct loader bug; install from a Git remote or copy hook to user settings
SessionStart hook with the "compact" matcher: stdout produced after compaction is dropped#15174Open. No clean workaround; emit context from a UserPromptSubmit hook instead
Plugin SessionStart hook fails on Windows: script never executes#21468Windows path/shell handling; use a wrapper .cmd or move to user settings

If your symptom is closer to "hook never runs" than "hook runs but Claude does not see the output", the user-settings move is still worth trying first because it bypasses the entire plugin loading path. After that, match your case to the table above before investigating further.

How to verify your hook is now injecting context

Add a unique marker string to your additionalContext value, restart Claude Code, and start a new session.

  1. In the transcript, the SessionStart line should not be the bare Callback hook success: Success. With user-settings hooks the runner shows the actual command output.
  2. Ask Claude a question that can only be answered by reading your marker string. If it answers correctly, the context is injected.
  3. If the hook produces a marker but Claude says "I do not know", the file is being read but the runtime is still discarding the output. Validate the JSON with a strict parser (trailing commas and BOM kill the load); see also our notes on Cursor rules silently failing to load for the same flavor of config-not-honored bug.

If you also use Claude Code's user settings file to register MCP servers, both the "hooks" and "mcpServers" blocks live alongside each other in the same JSON. Keep them in one file rather than splitting across project and user settings.

FAQ

Why does the same hook config work in settings.json but not in hooks.json?

The plugin loader walks the plugin's hooks.json, executes the SessionStart hook process, then drops the stdout JSON instead of parsing it for hookSpecificOutput. The user-settings loader runs the same process and parses the JSON correctly. It is a bug in the plugin loader, not in the hook runner.

How do I tell if my SessionStart hook is firing at all?

Add a side effect to the hook script that does not depend on stdout: write a line to /tmp/session-start.log with a timestamp. Start a new Claude Code session and check the log. If the line appears, the hook is firing and the bug is the dropped output (#16538). If the line does not appear, the hook is not firing and you are in #10373, #11509, or #21468 territory.

Does this affect UserPromptSubmit or PreToolUse hooks in plugins?

No. Issue #16538 is specific to SessionStart. UserPromptSubmit and PreToolUse hooks in a plugin's hooks.json surface their additionalContext correctly. A separate bug, #17550, affects UserPromptSubmit on the very first message of a session, but it resolves itself on the second message.

Will moving the hook to settings.json break the plugin's distribution?

It changes the install story. Anything else the plugin does (commands, agents, output styles) still ships normally; only the SessionStart hook needs a manual paste. Document it in the plugin README and accept that there is no automation until Anthropic ships a fix.

Is there a fix coming?

The issue has been labelled has repro and bug by Anthropic but is currently marked stale with no assignee as of 2026. Watch the issue thread for updates rather than rebuilding around speculation.