Problem
The session-start.sh hook produces zero visible output in Claude Code sessions, even though the script executes successfully. The meta-skill using-agent-skills is silently discarded on every session start.
Step-by-Step Root Cause Analysis
Layer 1: Invalid Output Format (Primary)
The hook outputs an undocumented JSON format that Claude Code does not recognize:
{"priority": "IMPORTANT", "message": "agent-skills loaded...\n\n[FULL SKILL.MD]"}
Why this fails:
-
The official Claude Code hooks reference (code.claude.com/docs/en/hooks.md) defines valid SessionStart hook output as:
- Plain text stdout — any non-JSON text is added directly as context
hookSpecificOutput.additionalContext — structured JSON with specific schema
systemMessage — universal top-level field
-
The "priority" / "message" top-level fields do not exist in the Claude Code hook schema. They are neither documented in the hooks reference nor in the agent-sdk hooks docs.
-
When Claude Code receives output starting with { but not matching the valid schema, it attempts to parse it as JSON. The unrecognized fields are silently ignored, and no context is injected.
-
This was verified by comparing with other plugins:
- superpowers (obra/superpowers) uses valid
hookSpecificOutput schema
- oh-my-claudecode uses plain text with budget guards
- claude-mem uses
{"continue":true,"suppressOutput":true} (valid universal fields)
Layer 2: Plugin Pipeline Bug (Secondary, Upstream)
Even if the format were fixed, Claude Code has a known upstream bug:
- Bug #16538 (anthropics/claude-code#16538): Plugin-defined
SessionStart hooks that output hookSpecificOutput.additionalContext have their context silently dropped. Native hooks defined in ~/.claude/settings.json work correctly.
- Affected events:
SessionStart, PreCompact
- Working events:
PostToolUse, UserPromptSubmit
This means even a correctly-formatted hookSpecificOutput from the plugin's hooks/hooks.json would still fail.
Verification Steps Performed
| Test |
Result |
Manual execution of session-start.sh |
✅ Script runs, outputs JSON |
Plugin enabled in settings.json |
✅ agent-skills@addy-agent-skills: true |
| Hook auto-merges from plugin |
✅ Confirmed via /hooks |
| Output visible in session context |
❌ Zero appearance |
| Native plain-text hook test |
✅ Works when defined in settings.json |
| Native broken-JSON hook test |
❌ Silent failure (confirms format is root cause) |
The Fix (What We Implemented)
1. Change Output Format
Replaced the invalid {"priority":"...","message":"..."} format with plain text stdout:
#!/bin/bash
# ...
if [ -f "$META_SKILL" ]; then
echo "agent-skills loaded. Project context detected. Most relevant skills are primed below."
echo ""
cat "$META_SKILL"
else
echo "agent-skills: meta-skill not found."
fi
2. Make the Hook "Smart"
Instead of dumping the entire 8.6KB SKILL.md into every session, the hook now uses tiered loading:
- Tier 1: Universal Core — always loaded (Core Operating Behaviors, Failure Modes, Skill Discovery Flowchart)
- Tier 2: Project Detection — detects project type from repo files (
package.json, Cargo.toml, etc.)
- Tier 3: Skill Mapping — maps project type to 3-5 most relevant skills
- Tier 4: Primer Extraction — loads only description + overview + "When to Use" from relevant skills
- Tier 5: Budget Guard — hard limit at 6000 chars (inspired by oh-my-claudecode's
SESSION_START_CONTEXT_BUDGET)
Example: For a docs/wiki project → primed: documentation-and-adrs, context-engineering
3. Bypass Plugin Pipeline Bug
To avoid Bug #16538:
-
Removed SessionStart from plugin's hooks/hooks.json:
-
Added native hook in ~/.claude/settings.json:
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash $(cd ~ && pwd)/.claude/plugins/cache/addy-agent-skills/agent-skills/1.0.0/hooks/session-start.sh",
"timeout": 10
}
]
}
]
Important: Used $(cd ~ && pwd) instead of ${HOME} because ${HOME} is not set in Claude Code hook environments.
-
Disabled auto-update to prevent the fix from being overwritten:
"extraKnownMarketplaces": {
"addy-agent-skills": {
"source": {"source": "github", "repo": "addyosmani/agent-skills"},
"autoUpdate": false
}
}
Suggested Fix for Upstream
Option A: Merge the Smart Hook (Recommended)
Replace hooks/session-start.sh with the smart version that:
- Uses plain text output (valid Claude Code format)
- Detects project type dynamically
- Loads only relevant skill primers
- Includes budget guard
- Uses
$(cd ~ && pwd) instead of ${HOME}
Option B: Minimal Fix (Quick)
If keeping the current architecture:
- Change output from
{"priority":"...","message":"..."} to plain text
- Keep dumping full
SKILL.md (accept the token cost)
- Document that users must add the hook natively in
settings.json to bypass Bug #16538
Option C: Use hookSpecificOutput.additionalContext
jq -nc --arg ctx "$CONTENT" '
{
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: $ctx
}
}'
Note: This still requires the native-hook workaround because Bug #16538 affects all plugin-defined SessionStart hooks.
Environment
- Claude Code versions tested: v2.1.39 – v2.1.86+ (issue confirmed across versions)
- Platform: macOS (darwin) — sed portability was also a concern
- jq installed: yes (jq-1.8.1)
- Plugin enabled: yes
- Hook executes: yes (verified manually and in debug logs)
- Output visible in session: no (before fix) / yes (after fix)
References
Problem
The
session-start.shhook produces zero visible output in Claude Code sessions, even though the script executes successfully. The meta-skillusing-agent-skillsis silently discarded on every session start.Step-by-Step Root Cause Analysis
Layer 1: Invalid Output Format (Primary)
The hook outputs an undocumented JSON format that Claude Code does not recognize:
{"priority": "IMPORTANT", "message": "agent-skills loaded...\n\n[FULL SKILL.MD]"}Why this fails:
The official Claude Code hooks reference (code.claude.com/docs/en/hooks.md) defines valid SessionStart hook output as:
hookSpecificOutput.additionalContext— structured JSON with specific schemasystemMessage— universal top-level fieldThe
"priority"/"message"top-level fields do not exist in the Claude Code hook schema. They are neither documented in the hooks reference nor in the agent-sdk hooks docs.When Claude Code receives output starting with
{but not matching the valid schema, it attempts to parse it as JSON. The unrecognized fields are silently ignored, and no context is injected.This was verified by comparing with other plugins:
hookSpecificOutputschema{"continue":true,"suppressOutput":true}(valid universal fields)Layer 2: Plugin Pipeline Bug (Secondary, Upstream)
Even if the format were fixed, Claude Code has a known upstream bug:
SessionStarthooks that outputhookSpecificOutput.additionalContexthave their context silently dropped. Native hooks defined in~/.claude/settings.jsonwork correctly.SessionStart,PreCompactPostToolUse,UserPromptSubmitThis means even a correctly-formatted
hookSpecificOutputfrom the plugin'shooks/hooks.jsonwould still fail.Verification Steps Performed
session-start.shsettings.jsonagent-skills@addy-agent-skills: true/hookssettings.jsonThe Fix (What We Implemented)
1. Change Output Format
Replaced the invalid
{"priority":"...","message":"..."}format with plain text stdout:2. Make the Hook "Smart"
Instead of dumping the entire 8.6KB
SKILL.mdinto every session, the hook now uses tiered loading:package.json,Cargo.toml, etc.)SESSION_START_CONTEXT_BUDGET)Example: For a docs/wiki project → primed:
documentation-and-adrs,context-engineering3. Bypass Plugin Pipeline Bug
To avoid Bug #16538:
Removed SessionStart from plugin's
hooks/hooks.json:{"hooks": {}}Added native hook in
~/.claude/settings.json:Important: Used
$(cd ~ && pwd)instead of${HOME}because${HOME}is not set in Claude Code hook environments.Disabled auto-update to prevent the fix from being overwritten:
Suggested Fix for Upstream
Option A: Merge the Smart Hook (Recommended)
Replace
hooks/session-start.shwith the smart version that:$(cd ~ && pwd)instead of${HOME}Option B: Minimal Fix (Quick)
If keeping the current architecture:
{"priority":"...","message":"..."}to plain textSKILL.md(accept the token cost)settings.jsonto bypass Bug #16538Option C: Use
hookSpecificOutput.additionalContextNote: This still requires the native-hook workaround because Bug #16538 affects all plugin-defined SessionStart hooks.
Environment
References
additionalContext: github.com/anthropics/claude-code/issues/16538CLAUDE_PLUGIN_ROOTnot set for SessionStart (fixed March 2026): github.com/affaan-m/everything-claude-code/issues/256