sessionStart.ts
utils/sessionStart.ts
233
Lines
8151
Bytes
3
Exports
11
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with session-engine. It contains 233 lines, 11 detected imports, and 3 detected exports.
Important relationships
Detected exports
takeInitialUserMessageprocessSessionStartHooksprocessSetupHooks
Keywords
hookshookresulterrormessagepluginadditionalcontextsincludesmessagehookmessagespushhookresultmessage
Detected imports
../bootstrap/state.js../types/message.js./attachments.js./debug.js./diagLogs.js./envUtils.js./hooks/fileChangedWatcher.js./hooks/hooksConfigSnapshot.js./hooks.js./log.js./plugins/loadPluginHooks.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { getMainThreadAgentType } from '../bootstrap/state.js'
import type { HookResultMessage } from '../types/message.js'
import { createAttachmentMessage } from './attachments.js'
import { logForDebugging } from './debug.js'
import { withDiagnosticsTiming } from './diagLogs.js'
import { isBareMode } from './envUtils.js'
import { updateWatchPaths } from './hooks/fileChangedWatcher.js'
import { shouldAllowManagedHooksOnly } from './hooks/hooksConfigSnapshot.js'
import { executeSessionStartHooks, executeSetupHooks } from './hooks.js'
import { logError } from './log.js'
import { loadPluginHooks } from './plugins/loadPluginHooks.js'
type SessionStartHooksOptions = {
sessionId?: string
agentType?: string
model?: string
forceSyncExecution?: boolean
}
// Set by processSessionStartHooks when a hook emits initialUserMessage;
// consumed once by takeInitialUserMessage. This side channel avoids changing
// the Promise<HookResultMessage[]> return type that main.tsx and print.ts
// both already await on (sessionStartHooksPromise is kicked in main.tsx and
// joined later — rippling a structural return-type change through that
// handoff would touch five callsites for what is a print-mode-only value).
let pendingInitialUserMessage: string | undefined
export function takeInitialUserMessage(): string | undefined {
const v = pendingInitialUserMessage
pendingInitialUserMessage = undefined
return v
}
// Note to CLAUDE: do not add ANY "warmup" logic. It is **CRITICAL** that you do not add extra work on startup.
export async function processSessionStartHooks(
source: 'startup' | 'resume' | 'clear' | 'compact',
{
sessionId,
agentType,
model,
forceSyncExecution,
}: SessionStartHooksOptions = {},
): Promise<HookResultMessage[]> {
// --bare skips all hooks. executeHooks already early-returns under --bare
// (hooks.ts:1861), but this skips the loadPluginHooks() await below too —
// no point loading plugin hooks that'll never run.
if (isBareMode()) {
return []
}
const hookMessages: HookResultMessage[] = []
const additionalContexts: string[] = []
const allWatchPaths: string[] = []
// Skip loading plugin hooks if restricted to managed hooks only
// Plugin hooks are untrusted external code that should be blocked by policy
if (shouldAllowManagedHooksOnly()) {
logForDebugging('Skipping plugin hooks - allowManagedHooksOnly is enabled')
} else {
// Ensure plugin hooks are loaded before executing SessionStart hooks.
// loadPluginHooks() may be called early during startup (fire-and-forget, non-blocking)
// to pre-load hooks, but we must guarantee hooks are registered before executing them.
// This function is memoized, so if hooks are already loaded, this returns immediately
// with negligible overhead (just a cache lookup).
try {
await withDiagnosticsTiming('load_plugin_hooks', () => loadPluginHooks())
} catch (error) {
// Log error but don't crash - continue with session start without plugin hooks
/* eslint-disable no-restricted-syntax -- both branches wrap with context, not a toError case */
const enhancedError =
error instanceof Error
? new Error(
`Failed to load plugin hooks during ${source}: ${error.message}`,
)
: new Error(
`Failed to load plugin hooks during ${source}: ${String(error)}`,
)
/* eslint-enable no-restricted-syntax */
if (error instanceof Error && error.stack) {
enhancedError.stack = error.stack
}
logError(enhancedError)
// Provide specific guidance based on error type
const errorMessage =
error instanceof Error ? error.message : String(error)
let userGuidance = ''
if (
errorMessage.includes('Failed to clone') ||
errorMessage.includes('network') ||
errorMessage.includes('ETIMEDOUT') ||
errorMessage.includes('ENOTFOUND')
) {
userGuidance =
'This appears to be a network issue. Check your internet connection and try again.'
} else if (
errorMessage.includes('Permission denied') ||
errorMessage.includes('EACCES') ||
errorMessage.includes('EPERM')
) {
userGuidance =
'This appears to be a permissions issue. Check file permissions on ~/.claude/plugins/'
} else if (
errorMessage.includes('Invalid') ||
errorMessage.includes('parse') ||
errorMessage.includes('JSON') ||
errorMessage.includes('schema')
) {
userGuidance =
'This appears to be a configuration issue. Check your plugin settings in .claude/settings.json'
} else {
userGuidance =
'Please fix the plugin configuration or remove problematic plugins from your settings.'
}
logForDebugging(
`Warning: Failed to load plugin hooks. SessionStart hooks from plugins will not execute. ` +
`Error: ${errorMessage}. ${userGuidance}`,
{ level: 'warn' },
)
// Continue execution - plugin hooks won't be available, but project-level hooks
// from .claude/settings.json (loaded via captureHooksConfigSnapshot) will still work
}
}
// Execute SessionStart hooks, ignoring blocking errors
// Use the provided agentType or fall back to the one stored in bootstrap state
const resolvedAgentType = agentType ?? getMainThreadAgentType()
for await (const hookResult of executeSessionStartHooks(
source,
sessionId,
resolvedAgentType,
model,
undefined,
undefined,
forceSyncExecution,
)) {
if (hookResult.message) {
hookMessages.push(hookResult.message)
}
if (
hookResult.additionalContexts &&
hookResult.additionalContexts.length > 0
) {
additionalContexts.push(...hookResult.additionalContexts)
}
if (hookResult.initialUserMessage) {
pendingInitialUserMessage = hookResult.initialUserMessage
}
if (hookResult.watchPaths && hookResult.watchPaths.length > 0) {
allWatchPaths.push(...hookResult.watchPaths)
}
}
if (allWatchPaths.length > 0) {
updateWatchPaths(allWatchPaths)
}
// If hooks provided additional context, add it as a message
if (additionalContexts.length > 0) {
const contextMessage = createAttachmentMessage({
type: 'hook_additional_context',
content: additionalContexts,
hookName: 'SessionStart',
toolUseID: 'SessionStart',
hookEvent: 'SessionStart',
})
hookMessages.push(contextMessage)
}
return hookMessages
}
export async function processSetupHooks(
trigger: 'init' | 'maintenance',
{ forceSyncExecution }: { forceSyncExecution?: boolean } = {},
): Promise<HookResultMessage[]> {
// Same rationale as processSessionStartHooks above.
if (isBareMode()) {
return []
}
const hookMessages: HookResultMessage[] = []
const additionalContexts: string[] = []
if (shouldAllowManagedHooksOnly()) {
logForDebugging('Skipping plugin hooks - allowManagedHooksOnly is enabled')
} else {
try {
await loadPluginHooks()
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error)
logForDebugging(
`Warning: Failed to load plugin hooks. Setup hooks from plugins will not execute. Error: ${errorMessage}`,
{ level: 'warn' },
)
}
}
for await (const hookResult of executeSetupHooks(
trigger,
undefined,
undefined,
forceSyncExecution,
)) {
if (hookResult.message) {
hookMessages.push(hookResult.message)
}
if (
hookResult.additionalContexts &&
hookResult.additionalContexts.length > 0
) {
additionalContexts.push(...hookResult.additionalContexts)
}
}
if (additionalContexts.length > 0) {
const contextMessage = createAttachmentMessage({
type: 'hook_additional_context',
content: additionalContexts,
hookName: 'Setup',
toolUseID: 'Setup',
hookEvent: 'Setup',
})
hookMessages.push(contextMessage)
}
return hookMessages
}