Hook Output Mechanisms: Context Cost Analysis¶
Report Date: 2025-10-25 Author: Claude Code Analysis Scope: Claude Code plugin hook output fields and token costs
1. Context Cost Analysis¶
feedback Field¶
- Token Cost: 0 tokens ✅
- Added to Claude's Context: NO
- Shown to User: YES (in UI)
- Visible in Transcript (Ctrl-R): YES (unless
suppressOutput: true)
Usage: Display information to users without consuming context tokens.
hookSpecificOutput.additionalContext Field¶
- Token Cost: Full text length (~4 chars per token) ⚠️
- Added to Claude's Context: YES
- Shown to User: NO (intended), YES (bug #9455)
- Visible in Transcript (Ctrl-R): Depends on
suppressOutput
Usage: Add persistent knowledge Claude needs across the session.
suppressOutput Field¶
- Token Cost: 0 tokens ✅
- Added to Claude's Context: NO
- Shown to User: Controlled by this flag
- Visible in Transcript (Ctrl-R): NO when
true
Usage: Hide hook output from transcript mode (Ctrl-R view).
continue Field¶
- Token Cost: 0 tokens ✅
- Purpose: Control whether Claude continues processing
- Values:
true(continue),false(stop with optionalstopReason)
2. Zero-Context Pattern¶
Objective: Show Promptune commands in UI without consuming tokens.
{
"continue": true,
"feedback": "💡 Promptune Commands:\n /promptune:config - Configure detection\n /promptune:stats - View statistics\n /promptune:verify - Verify detection\n\nOr type naturally - I'll detect your intent!",
"suppressOutput": false
}
Results:
- Token cost: 0 tokens
- User sees: Full command list with descriptions
- Claude sees: Nothing (no context added)
- Transcript visibility: YES (useful for user reference)
When to Use:
- Command lists at session start
- UI notifications and hints
- Status messages
- Non-critical information
3. Implementation Guide¶
SessionStart Hook: Zero-Context Command List¶
File: /Users/promptune/DevProjects/promptune/hooks/session_start.js
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
function getPromptuneCommands() {
const pluginRoot =
process.env.CLAUDE_PLUGIN_ROOT || path.join(__dirname, "..");
const commandsDir = path.join(pluginRoot, "commands");
if (!fs.existsSync(commandsDir)) {
return [];
}
const commands = [];
const files = fs.readdirSync(commandsDir);
for (const file of files) {
if (file.endsWith(".md")) {
try {
const content = fs.readFileSync(path.join(commandsDir, file), "utf8");
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (frontmatterMatch) {
const frontmatter = frontmatterMatch[1];
const nameMatch = frontmatter.match(/name:\s*(.+)/);
const descMatch = frontmatter.match(/description:\s*(.+)/);
if (nameMatch) {
commands.push({
name: `/${nameMatch[1].trim()}`,
description: descMatch ? descMatch[1].trim() : "",
});
}
}
} catch (err) {
// Ignore parsing errors
}
}
}
return commands;
}
function formatCommandList(commands) {
if (commands.length === 0) {
return "💡 Promptune is ready! Type naturally and I'll detect commands.";
}
const lines = ["💡 Promptune Commands Available:", ""];
for (const cmd of commands) {
lines.push(` ${cmd.name}`);
if (cmd.description) {
lines.push(` ${cmd.description}`);
}
}
lines.push("", "Or just type naturally - I'll detect your intent!");
return lines.join("\n");
}
function main() {
try {
const commands = getPromptuneCommands();
const message = formatCommandList(commands);
// ZERO-CONTEXT PATTERN: UI message only, no token cost
const output = {
continue: true,
feedback: message,
suppressOutput: false,
};
console.log(JSON.stringify(output));
process.exit(0);
} catch (err) {
// Fail silently - don't block session start
console.error("SessionStart hook error:", err.message);
process.exit(0);
}
}
if (require.main === module) {
main();
}
Hook Registration¶
File: /Users/promptune/DevProjects/promptune/hooks/hooks.json
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/session_start.js",
"timeout": 1000,
"description": "Show Promptune commands (zero context cost)"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "uv run ${CLAUDE_PLUGIN_ROOT}/hooks/user_prompt_submit.py",
"timeout": 5000,
"description": "Promptune intent detection"
}
]
}
]
}
}
Performance Characteristics¶
| Hook Type | Pattern | Token Cost | Latency | Use Case |
|---|---|---|---|---|
| SessionStart | Zero-context | 0 tokens | <1ms | Command list UI |
| SessionStart | Context injection | 100-500 tokens | <1ms | Config/state |
| UserPromptSubmit | Context injection | ~20 tokens/match | 2-50ms | Detected commands |
4. Multi-Hook Coordination¶
Sequential Execution Order¶
{
"hooks": {
"SessionStart": [
{
"hooks": [
{ "command": "hook1.js" }, // Runs first
{ "command": "hook2.js" } // Runs second
]
}
]
}
}
Context Combination Rules¶
- Multiple
additionalContext: Concatenated in execution order - Multiple
feedback: Implementation-dependent (possible bug) - Each hook: Independent
suppressOutputcontrol
Best Practice:
- ONE hook for context injection (persistent knowledge)
- SEPARATE hook for UI feedback (zero-context)
5. Token Cost Examples¶
Example 1: Minimal Command List (0 tokens)¶
{
"continue": true,
"feedback": "💡 Promptune: 3 commands available. Type naturally!",
"suppressOutput": false
}
Token cost: 0 (feedback not added to context)
Example 2: Detailed Command List (0 tokens)¶
{
"continue": true,
"feedback": "💡 Promptune Commands:\n /promptune:config - Configure detection settings\n /promptune:stats - View usage statistics\n /promptune:verify - Verify detected command\n\nOr type naturally - I'll detect your intent!",
"suppressOutput": false
}
Token cost: 0 (still zero - feedback is UI-only)
Example 3: Context Injection (~100 tokens)¶
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "[Promptune Configuration]\n- Custom patterns: 5 commands\n- Detection tiers: keyword, Model2Vec, semantic\n- Min confidence: 0.7\n- Fallback: prompt user if < 0.5"
},
"feedback": "💡 Promptune loaded with custom config",
"suppressOutput": true
}
Token cost: ~100 tokens (additionalContext added to Claude's context)
Example 4: Both Patterns (~100 tokens)¶
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "[Promptune: 10 custom patterns loaded]"
},
"feedback": "💡 Promptune ready with your custom patterns!\n\nCommands:\n /promptune:config\n /promptune:stats\n\nType naturally for intent detection.",
"suppressOutput": false
}
Token cost: ~15 tokens (only additionalContext counts)
6. Recommendations¶
For Promptune Plugin¶
SessionStart Hook - Use zero-context pattern:
// RECOMMENDED: Show commands without context cost
{
"continue": true,
"feedback": formatCommandList(commands),
"suppressOutput": false
}
Rationale:
- Users see available commands immediately
- Zero token overhead (important for long sessions)
- Commands detected via UserPromptSubmit hook anyway
- Claude doesn't need to "know" commands in advance
UserPromptSubmit Hook - Keep context injection:
// KEEP: Inform Claude of detected command
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": `[Promptune detected: ${match.command}]`
},
"feedback": `💡 Suggested: ${match.command}`,
"suppressOutput": false
}
Rationale:
- Claude needs to know detected command (~20 tokens)
- User sees suggestion in UI (helpful feedback)
- Total cost per session: 20-100 tokens (acceptable)
General Guidelines¶
Use feedback (0 tokens) for:
- ✅ Command lists and menus
- ✅ Status notifications
- ✅ UI hints and tips
- ✅ Help text and documentation
- ✅ Diagnostic information
Use additionalContext (token cost) for:
- ✅ Configuration Claude needs to remember
- ✅ Detected commands/intents
- ✅ Project state and context
- ✅ Custom rules and patterns
- ✅ Session-specific knowledge
Use suppressOutput: true for:
- ✅ Context injection (reduce UI noise)
- ✅ Internal diagnostics
- ✅ Background processing
- ✅ When feedback is redundant
7. Known Issues & Bugs¶
Issue #9455: additionalContext Shown to User¶
Expected: additionalContext should be invisible to user, only added to Claude's context
Actual: Sometimes displayed at session start
Workaround: Use suppressOutput: true to minimize visibility
Impact: Cosmetic (doesn't affect functionality)
Status: Under investigation by Anthropic
Conclusion¶
Key Findings:
feedbackhas ZERO context cost - use liberally for UIadditionalContexthas FULL token cost - use sparingly for knowledge- Zero-context pattern enables rich UI without token overhead
- Multi-hook coordination allows separation of concerns
Promptune Implementation:
- SessionStart: Zero-context command list (0 tokens)
- UserPromptSubmit: Context injection for matches (~20 tokens/match)
- Total overhead: Minimal (20-100 tokens per session)
Performance: Excellent UX with minimal token cost.
Working Examples¶
See implementation examples:
/Users/promptune/DevProjects/promptune/examples/session_start_zero_context.js/Users/promptune/DevProjects/promptune/examples/session_start_with_context.js
Full analysis:
/Users/promptune/DevProjects/promptune/docs/hook-output-analysis.md