What happened
After migrating a PAI 5.0.0 install to LifeOS 6.0.5 (July 2026), 8 of 41 registered hooks could not load for about three months:
| Hook |
Error at load |
| Safety.hook.ts |
Cannot find module './lib/safety-classifier' |
| SystemFileGuard.hook.ts |
Cannot find module './lib/system-file-guard-core' |
| TheRouter.hook.ts |
Export named 'setModeToken' not found in './lib/tab-setter' |
| PromptProcessing.hook.ts |
Cannot find module './lib/notification-channel' |
| VoiceCompletion.hook.ts |
Cannot find module './lib/notification-channel' |
| ISASync.hook.ts |
Export named 'bumpLastToolActivityBySlug' not found in './lib/isa-utils' |
| DocIntegrity.hook.ts |
Cannot find module './handlers/MemoryDirIntegrity' |
| OutputFormatGate.hook.ts |
Cannot find module './lib/ai-speak-patterns' |
Plus LoadContext, SessionCleanup and WorkCompletionLearning (missing getLifeosDir / findActiveSessionByUUID).
Claude Code treats a hook that exits non-zero as non-blocking, so every one of these failed on every event with nothing more than an occasional hook error line. Nothing in LifeOS surfaced it: HookHealer, ConfigAudit and the memory health grade all stayed quiet. The only reason it was found was a manual health check.
Root cause (on our side, to be clear)
The installer is not at fault for the file layout. InstallHooks.ts uses cpSync(..., { recursive: true }), which merges correctly. Our migration skipped it: because the Setup workflow has no PAI 5 to LifeOS content path, the migration was done by hand, and a one-off script ran cp -R install/hooks/lib ~/.claude/hooks/lib while hooks/lib already existed. That nested the new libraries at hooks/lib/lib/ and hooks/handlers/handlers/, leaving the stale PAI 5 copies in place.
So the trigger was user-side. The upstream issue is that any path that leaves a hook's imports unresolved (hand migrations, partial updates, a stray file deletion, a renamed export in a future release) silently disables that hook, including the security ones.
Suggestions
- Post-install / post-update load check. After
InstallHooks.ts (and in Update), statically verify that every hook registered in settings.json resolves its relative imports and named exports, and print any failures loudly. A ~40-line check (parse each hook's import { a, b } from './...', compare to the target file's export names) found all 11 failures here in under 2 seconds.
- Run the same check from HookHealer at SessionStart, and surface a failure on the statusline or in Pulse. A one-line "3 hooks failing to load" would have caught this on day one.
- Consider failing closed for Safety. If
Safety.hook.ts can't load its classifier, that's currently indistinguishable from "allowed everything to fall through to the default prompt." A tiny wrapper that catches the import failure and emits a visible warning (or denies until fixed) would make that state impossible to miss.
- (Optional) A documented PAI 5 to LifeOS migration path in the Setup workflow, so people upgrading don't hand-roll file copies.
Happy to share the check script if it's useful.
Environment: macOS 26 (arm64), Bun 1.3.11, Claude Code 2.1.234, LifeOS 6.0.5 (Algorithm v6.24.0).
What happened
After migrating a PAI 5.0.0 install to LifeOS 6.0.5 (July 2026), 8 of 41 registered hooks could not load for about three months:
Plus LoadContext, SessionCleanup and WorkCompletionLearning (missing
getLifeosDir/findActiveSessionByUUID).Claude Code treats a hook that exits non-zero as non-blocking, so every one of these failed on every event with nothing more than an occasional
hook errorline. Nothing in LifeOS surfaced it: HookHealer, ConfigAudit and the memory health grade all stayed quiet. The only reason it was found was a manual health check.Root cause (on our side, to be clear)
The installer is not at fault for the file layout.
InstallHooks.tsusescpSync(..., { recursive: true }), which merges correctly. Our migration skipped it: because the Setup workflow has no PAI 5 to LifeOS content path, the migration was done by hand, and a one-off script rancp -R install/hooks/lib ~/.claude/hooks/libwhilehooks/libalready existed. That nested the new libraries athooks/lib/lib/andhooks/handlers/handlers/, leaving the stale PAI 5 copies in place.So the trigger was user-side. The upstream issue is that any path that leaves a hook's imports unresolved (hand migrations, partial updates, a stray file deletion, a renamed export in a future release) silently disables that hook, including the security ones.
Suggestions
InstallHooks.ts(and inUpdate), statically verify that every hook registered insettings.jsonresolves its relative imports and named exports, and print any failures loudly. A ~40-line check (parse each hook'simport { a, b } from './...', compare to the target file'sexportnames) found all 11 failures here in under 2 seconds.Safety.hook.tscan't load its classifier, that's currently indistinguishable from "allowed everything to fall through to the default prompt." A tiny wrapper that catches the import failure and emits a visible warning (or denies until fixed) would make that state impossible to miss.Happy to share the check script if it's useful.
Environment: macOS 26 (arm64), Bun 1.3.11, Claude Code 2.1.234, LifeOS 6.0.5 (Algorithm v6.24.0).