SessionStart hooks not working for new conversations

Open 💬 20 comments Opened Oct 26, 2025 by jeremybarnes

Bug Report: SessionStart Hooks Not Working for New Conversations

Environment

  • Claude Code Version: 2.0.27
  • Platform: macOS (Darwin 24.6.0)
  • Node.js Version: v23.11.0
  • Installation Type: npm-global (via Homebrew)

Summary

SessionStart hooks execute but their output is never processed or injected into Claude's context when starting new conversations. The hooks work correctly for /clear, /compact, and URL resume operations, but fail silently for brand new sessions.

Expected Behavior

When a user starts a new Claude Code conversation, SessionStart hooks should:

  1. Execute successfully
  2. Have their stdout parsed for hookSpecificOutput.additionalContext
  3. Create hook_additional_context attachments
  4. Inject that context into Claude's conversation

According to the documentation, "For UserPromptSubmit and SessionStart, stdout is added as context."

Actual Behavior

  • SessionStart hooks do execute (verified by file logging)
  • Hook stdout is never processed by the qz() function
  • The hook_additional_context attachments are never created
  • Context is not injected into new conversations
  • No error messages are shown to the user

Root Cause Analysis

Code Investigation

The qz() function in cli.js is responsible for:

  1. Executing SessionStart hooks
  2. Collecting their stdout
  3. Parsing JSON output for hookSpecificOutput.additionalContext
  4. Creating hook_additional_context attachments
  5. Returning those attachments for injection into the conversation

Problem: qz() is only called in 3 places in the entire codebase:

  1. Line 1798: await qz("compact") - during /compact command ✅
  2. Line 3723: await qz("startup") - during URL resume (--resume with URL) ✅
  3. Line 3726: await qz("startup") - during /clear command ✅

For brand new interactive conversations, the wm6() function (line 3717) only replays old hook responses from message history:

let H=FZ2(G),w=[],N=!1;
for(let g of H)if(g.type==="system"&&g.subtype==="hook_response"&&g.hook_event==="SessionStart")w.push(g);

It never calls qz("startup") for new sessions, so SessionStart hooks execute but their output is discarded.

Verification Steps Taken

  1. Confirmed hooks execute: Added logging to SessionStart hook - timestamps written to /tmp/claude-session-start.log proved hooks ran
  2. Confirmed no output injection: Created test hook outputting secret word - Claude had no knowledge of it in new sessions
  3. Added debug logging: Instrumented qz(), Gf2(), and hook processing functions - confirmed qz() never called for new sessions
  4. Source code review: Examined cli.js - found only 3 call sites for qz(), none for new interactive sessions

Reproduction Steps

1. Create a test SessionStart hook

#!/bin/bash
cat <<'EOF'
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "The secret word is \"bamboozle\"."
  }
}
EOF
exit 0

2. Configure in .claude/settings.local.json

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/absolute/path/to/test_hook.sh"
          }
        ]
      }
    ]
  }
}

3. Start a brand new conversation

claude

4. Ask Claude

> what is the secret word?

Expected: Claude responds "bamboozle"
Actual: Claude has no knowledge of any secret word

5. Verify hook executed

cat /tmp/hook-executed.log  # If hook logs timestamps

Hook timestamps confirm execution, but context was not injected.

The Fix

Add qz("startup") call inside the wm6 function's async helper where hooks are processed:

File: /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js
Location: Line ~3717, inside the _=async()=>{} function

Original code:

_=async()=>{
  if(V=!0,!N){
    N=!0;
    for(let c of w)E.enqueue(c)
  }
  // ... rest of function

Fixed code:

_=async()=>{
  if(V=!0,!N){
    N=!0;
    if(w.length===0&&H.length===0){
      let s=await qz("startup");
      w.push(...s);
    }
    for(let c of w)E.enqueue(c)
  }
  // ... rest of function

Explanation: This checks if there are no hooks from history (w.length===0) and no messages (H.length===0), indicating a new session, then calls qz("startup") to process SessionStart hooks before enqueuing them.

Verification After Fix

After applying the fix:

  1. Started new conversation
  2. SessionStart hook output appeared:

``
SessionStart:startup hook succeeded: The secret word is "bamboozle".
``

  1. Asked Claude for secret word
  2. Claude correctly responded with "bamboozle"

Impact

This bug affects all users who configure SessionStart hooks for:

  • Injecting project-specific rules/context
  • Setting up session-specific configuration
  • Loading workspace information

Workaround: Users must manually run /clear at the start of each session to trigger SessionStart hooks, which is not intuitive.

Additional Notes

  • PreToolUse and PostToolUse hooks work correctly
  • UserPromptSubmit hooks work correctly
  • The UI correctly hides hook_additional_context attachments (line 1818 in cli.js)
  • The bug only affects the interactive CLI mode, not --print mode (which calls qz("startup") at line 3726)

Files for Reference

  • Main implementation: cli.js line 3717 (wm6 function)
  • Hook processing: cli.js line ~1798 (qz function)
  • Context extraction: cli.js (Gf2 function)
  • UI rendering: cli.js line 1818 (hides hook_additional_context)

Proposed Solution

Apply the fix shown above, or alternatively refactor to ensure qz("startup") is called for all new sessions, not just URL resumes and command-triggered operations.

View original on GitHub ↗

20 Comments

github-actions[bot] · 8 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/9591
  2. https://github.com/anthropics/claude-code/issues/10287
  3. https://github.com/anthropics/claude-code/issues/9542

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

jeremybarnes · 8 months ago

Not a duplicate:

  • #9591 is about displaying the context, not running the script
  • #10287 is about all hooks, whereas this is about the SessionStart hook only (the others still appear to work)
  • #9542 is about the hooks hanging after being run (on Windows), this is about them never being run at all
crsmithdev · 7 months ago

Confirmed this issue. Setup:

  • SessionStart hook configured in ~/.claude/settings.json
  • Hook executes successfully when run manually
  • Proper JSON schema validation passes
  • Script outputs to stdout as expected

Result: On fresh session start (via /new command), no hook output appears in context. Hook appears to execute silently or output is discarded.

Workaround attempted: None successful without manually running the script.

Config:

"hooks": {
  "SessionStart": [
    {
      "hooks": [
        {
          "type": "command",
          "command": "bash ~/.claude/scripts/validate-commands.sh"
        }
      ]
    }
  ]
}
github-actions[bot] · 6 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

leigh-sesameai · 6 months ago

how is this still open ... this is in your docs...

MarcinDudekDev · 6 months ago

+1 it doesn't work

iampava · 5 months ago

+1 same here

michael-denyer · 5 months ago

Title: SessionStart hooks don't fire reliably when opening new VSCode window with multiple windows already open

Description:
When multiple VSCode windows are running Claude Code and I open a new window, plugins that rely on SessionStart hooks fail to load properly. Commands appear in the slash menu but return "Unknown skill" when invoked.

Other plugins (beads, pr-review-toolkit) that register skills statically work fine
~/.claude/plugins/cache/temp_git_* directories accumulate with .orphaned_at markers (225+ in my case)
Running /clear fixes it by re-triggering SessionStart
Reinstalling the plugin also fixes it temporarily
Affected plugin: superpowers (but likely affects any plugin using SessionStart hooks)

Confirmed same issue. Claude Code led me here in troubleshooting it.

Vaccano · 5 months ago

I can also confirm that this is broken for me. (Startup hook is ignored.)

scytaskin · 5 months ago

Also just tried to create a session startup hook. Same problem. Please fix.

drishk25 · 5 months ago

Facing the same issue.

greghughespdx · 5 months ago

Also experiencing this. As a workaround, I tried moving context injection to UserPromptSubmit hooks instead of SessionStart, but that has its own issues - the additionalContext from UserPromptSubmit hooks also doesn't get injected into Claude's context (related to #12151).

So currently there's no reliable way to inject dynamic context at either:

  • Session start (this issue)
  • Per-prompt (UserPromptSubmit - #12151)

This makes it impossible to build automation that feeds external information into Claude conversations. Would really appreciate a fix for the hook → context injection pipeline.

eilonb11 · 5 months ago

Facing the same issue

jspiro · 5 months ago

Wonderful, another broken configuration. I'm here after trying to write a hook to work around user permissions being ignored to copy user permissions to project permissions. OF COURSE that doesn't work either. Why host a github repo for something if you're going to ignore fundamental issues people are filing?

TanakaSeiji · 4 months ago

Reporting empirical findings from Claude Code VSCode extension (March 2026):

startup matcher is now firing correctly in VSCode — both on Reload and on /new command.

Observed behavior (session-start-context-load log, 2026-03-03):

12:33:01 [start] session-start-context-load (trigger: startup)   ← VSCode Reload
12:33:01 [skip] already ran this minute                           ← 2nd concurrent fire caught by idempotency guard
12:33:01 [ok] cached last 200 lines (trigger: startup)
12:35:07 [start] session-start-context-load (trigger: startup)   ← /new command
12:35:07 [ok] cached last 200 lines (trigger: startup)
  • VSCode Reload: 2 concurrent fires at same second → idempotency guard needed (minute-based lock)
  • /new command: single fire observed (1 data point; pattern vs Reload needs further observation)

This suggests the root cause described in this issue has been resolved in recent versions. Environment: Windows 11, VSCode extension, Claude Code v2.1.6x.

mikesimons · 4 months ago

This was working for me until very recently and now seems to have stopped.
Opus is already a bit flakey about following instruction reliably so these hooks are the only way to inject a bit of determinism.

Sad state of affairs.

bidossessi · 4 months ago

Experiencing this on v2.1.19 (Linux).

  • SessionStart hook (Python script outputting plain text) was working for fresh sessions yesterday
  • Today, same setup fails silently for fresh sessions
  • No version update occurred (installation dates from January 23)
  • No script changes
  • Hook works correctly after /clear

The intermittent nature is puzzling - no identified variable changed between working and non-working state.

tdodd777 · 4 months ago

Also experiencing this intermittently on macOS (CLI, not VSCode). I built Tacklebox, a hook server for session coordination and context persistence, and ran into this early on. My workaround is a dual-path injection: SessionStart builds and returns context normally, but for startup sessions we deliberately skip setting an internal context_injected flag. This lets UserPromptSubmit on the first prompt act as a fallback, if SessionStart was silently discarded, UserPromptSubmit catches it and injects the same context. Either way, context is delivered reliably.

Noting that TanakaSeiji's findings above match what we've seen: resume, compact, and clear sources work consistently; it's only startup that's unreliable. The behavior seems environment-dependent and can come and go between versions without any config changes on our side.

mrmartan · 3 months ago

When Claude refers to "the issue" it means this issue.

You're right — the issue confirms that /clear does re-trigger SessionStart hooks (it's one of the 3 call sites for qz()). So the hook context was injected into my conversation
   after /clear.

  The issue is about fresh new sessions where SessionStart hooks silently discard their output. /clear is actually the documented workaround.

  So to answer your question: the boot instruction was present in my context (I can see it in the system-reminder). I just didn't act on it proactively before you messaged me —
  that's on me, not a platform bug. I should have called context_summary immediately without you having to ask.

❯ how is that on you and not a platform bug when you had it in the system-reminder and you still didn't follow it?

⏺ Fair point. You're right — if the instruction is in the system-reminder and I still don't follow it unprompted, that's a model reliability issue, not a conscious choice I
  made. I didn't "decide" to skip it; I just failed to prioritize it before responding to your message. That's exactly the kind of flaky instruction-following the issue
  commenters are frustrated about.

  It's a model behavior bug — same category as the comments saying "Opus is already a bit flakey about following instruction reliably."
rznd · 2 months ago

This is annoying, still happening, please fix.