(
source: 'startup' | 'resume' | 'clear' | 'compact',
{
sessionId,
agentType,
model,
forceSyncExecution,
}: SessionStartHooksOptions = {},
)
| 36 | |
| 37 | // Note to CLAUDE: do not add ANY "warmup" logic. It is **CRITICAL** that you do not add extra work on startup. |
| 38 | export async function processSessionStartHooks( |
| 39 | source: 'startup' | 'resume' | 'clear' | 'compact', |
| 40 | { |
| 41 | sessionId, |
| 42 | agentType, |
| 43 | model, |
| 44 | forceSyncExecution, |
| 45 | }: SessionStartHooksOptions = {}, |
| 46 | ): Promise<HookResultMessage[]> { |
| 47 | // --bare skips all hooks. executeHooks already early-returns under --bare |
| 48 | // (hooks.ts:1861), but this skips the loadPluginHooks() await below too — |
| 49 | // no point loading plugin hooks that'll never run. |
| 50 | if (isBareMode()) { |
| 51 | return [] |
| 52 | } |
| 53 | const hookMessages: HookResultMessage[] = [] |
| 54 | const additionalContexts: string[] = [] |
| 55 | const allWatchPaths: string[] = [] |
| 56 | |
| 57 | // Skip loading plugin hooks if restricted to managed hooks only |
| 58 | // Plugin hooks are untrusted external code that should be blocked by policy |
| 59 | if (shouldAllowManagedHooksOnly()) { |
| 60 | logForDebugging('Skipping plugin hooks - allowManagedHooksOnly is enabled') |
| 61 | } else if (isDeferredHeadlessPluginStartupSurfacesEnabled()) { |
| 62 | logForDebugging( |
| 63 | '[STARTUP] Skipping plugin hook loading during deferred headless startup', |
| 64 | ) |
| 65 | } else { |
| 66 | // Ensure plugin hooks are loaded before executing SessionStart hooks. |
| 67 | // loadPluginHooks() may be called early during startup (fire-and-forget, non-blocking) |
| 68 | // to pre-load hooks, but we must guarantee hooks are registered before executing them. |
| 69 | // This function is memoized, so if hooks are already loaded, this returns immediately |
| 70 | // with negligible overhead (just a cache lookup). |
| 71 | try { |
| 72 | await withDiagnosticsTiming('load_plugin_hooks', () => loadPluginHooks()) |
| 73 | } catch (error) { |
| 74 | // Log error but don't crash - continue with session start without plugin hooks |
| 75 | /* eslint-disable no-restricted-syntax -- both branches wrap with context, not a toError case */ |
| 76 | const enhancedError = |
| 77 | error instanceof Error |
| 78 | ? new Error( |
| 79 | `Failed to load plugin hooks during ${source}: ${error.message}`, |
| 80 | ) |
| 81 | : new Error( |
| 82 | `Failed to load plugin hooks during ${source}: ${String(error)}`, |
| 83 | ) |
| 84 | /* eslint-enable no-restricted-syntax */ |
| 85 | |
| 86 | if (error instanceof Error && error.stack) { |
| 87 | enhancedError.stack = error.stack |
| 88 | } |
| 89 | |
| 90 | logError(enhancedError) |
| 91 | |
| 92 | // Provide specific guidance based on error type |
| 93 | const errorMessage = |
| 94 | error instanceof Error ? error.message : String(error) |
| 95 | let userGuidance = '' |
no test coverage detected