[BUG] SessionStart hooks don't execute on first run with GitHub marketplace plugins
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?
SessionStart hooks do not execute on the first Claude Code run when plugins are loaded from GitHub marketplaces via extraKnownMarketplaces in .claude/settings.json. The hooks only execute on subsequent runs after the marketplace has been cached locally.
The test plugin's SessionStart hook should create a file named SESSION_START_HOOK_COMPLETE in the working directory when Claude Code starts. On the first run, this file is not created. On subsequent runs, the file is created successfully.
Note: This issue only occurs in interactive mode where process.stdout.isTTY is true. Non-interactive mode (using -p flag) does not load marketplaces at all, which is separate behavior.
What Should Happen?
SessionStart hooks should execute every time Claude Code starts a session, including the first run when a marketplace is newly configured.
Error Messages/Logs
No error messages are displayed. The hook silently fails to execute on first run.
Steps to Reproduce
Quick Reproduction (Automated Test Script)
- Clone the demonstration repository:
``bash``
git clone https://github.com/goodfoot-io/plugin-load-timing-issue-reproduction.git
cd plugin-load-timing-issue-reproduction
- Run the automated test script:
``bash``
chmod +x ./test-sessionstart-hook.sh
./test-sessionstart-hook.sh
Expected output:
==========================================
SessionStart Hook Test Script
==========================================
[Step 1] Running first Claude command in interactive mode...
Started Claude with pseudo-TTY (PID: 90703), waiting 10 seconds...
Killed Claude process
[Step 2] Checking for SESSION_START_HOOK_COMPLETE file after first run...
❌ SESSION_START_HOOK_COMPLETE does NOT exist after first run
[Step 3] Running second Claude command...
Started Claude with pseudo-TTY (PID: 90915), waiting 10 seconds...
Killed Claude process
[Step 4] Checking for SESSION_START_HOOK_COMPLETE file after second run...
✅ SESSION_START_HOOK_COMPLETE exists after second run
Content:
SessionStart hook executed at Tue Nov 4 01:57:33 PM EST 2025
[Step 5] Removing plugin...
✔ Successfully uninstalled plugin: sessionstart-hook-demonstration
[Step 6] Removing marketplace...
✔ Successfully removed marketplace: plugin-load-timing
[Step 7] Cleaning up SESSION_START_HOOK_COMPLETE file...
✅ Removed SESSION_START_HOOK_COMPLETE
==========================================
Test script complete!
==========================================
Manual Reproduction
- Configure
.claude/settings.jsonwith the GitHub marketplace and enabled plugin:
``json``
{
"extraKnownMarketplaces": {
"plugin-load-timing": {
"source": {
"source": "github",
"repo": "goodfoot-io/plugin-load-timing-issue-reproduction-marketplace"
}
}
},
"enabledPlugins": {
"sessionstart-hook-demonstration@plugin-load-timing": true
}
}
- Ensure clean state (no cached marketplace):
``bash``
claude plugin marketplace remove "plugin-load-timing" 2>/dev/null || true
claude plugin remove "sessionstart-hook-demonstration@plugin-load-timing" 2>/dev/null || true
rm -f SESSION_START_HOOK_COMPLETE
- Run Claude Code in interactive mode:
``bash``
claude
Wait for Claude to start, then exit (press Escape twice or Ctrl+C).
- Verify SessionStart hook did not execute:
``bash``
ls SESSION_START_HOOK_COMPLETE # File should NOT exist
- Run Claude Code again:
``bash``
claude
Wait for Claude to start, then exit (press Escape twice or Ctrl+C).
- Verify SessionStart hook now executes:
``bash``
cat SESSION_START_HOOK_COMPLETE # File should exist with timestamp
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.0.32
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
VS Code integrated terminal
Additional Information
About the Test Plugin
The plugin contains a SessionStart hook that creates a file named SESSION_START_HOOK_COMPLETE in the current working directory with a timestamp. This provides clear evidence of whether the hook executed.
Repositories
- Demonstration repository (contains test script): https://github.com/goodfoot-io/plugin-load-timing-issue-reproduction
- Marketplace repository (contains the plugin): https://github.com/goodfoot-io/plugin-load-timing-issue-reproduction-marketplace
Root Cause
Claude Code loads marketplaces asynchronously. The SessionStart hook fires before the asynchronous marketplace fetch completes on first run. On subsequent runs, the cached marketplace loads synchronously and plugins register before SessionStart fires.
Timeline (First Run):
- Settings loaded
- Marketplace fetch begins (async, GitHub download)
- SessionStart hooks fire (plugins not yet registered)
- Marketplace fetch completes (too late)
Timeline (Subsequent Runs):
- Settings loaded
- Cached marketplace loads (instant)
- Plugins registered
- SessionStart hooks fire (plugins available)
Impact
- Initialization tasks in SessionStart hooks fail on first run
- Inconsistent behavior between first and subsequent runs
- No error indication when hooks do not execute
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗