Skip to content

Installer ships 39 hook scripts and registers 28; nothing compares the two inventories (follow-up to #1508) #1539

Description

@tzioup

The installer ships 39 hook scripts and registers 28; nothing compares the two inventories (follow-up to #1508)

Structured for machine parsing: claims are atomic, and each carries the command that verifies it. Every command runs against a fresh clone of this repo — none depend on my machine, my install, or my configuration. Reproduction is at the bottom if you'd rather run it than read it.

Summary

InstallHooks.ts performs two operations and verifies each independently:

  1. cpSync the entire payload hooks/ tree onto disk — verified by a file count (hookFiles vs hookFilesCopied).
  2. Merge hooks/hooks.json into settings.json — verified by reporting added, skipped, events.

Both succeed. Neither is wrong. Nothing compares the set of files copied against the set of hooks registered. 39 hook scripts land on disk; 28 are named by the manifest merged beside them. The install reports success accurately, because "validated" here means "both operations completed," never "the things I copied are the things I registered."

Of the 11-hook difference, 8 are legitimately loaded via dispatcher imports. Three are unreachable: DriftReminder, PostToolObserver, and LoopDetector (whose only importer is PostToolObserver).

Doctor.ts --reconcile — which you shipped in v7.1.1 for exactly this, and which the same install run copies onto disk — reports all three correctly in under a second. The installer does not call it.

Claims and verification

Run from the root of a fresh clone.

C1 — the payload ships 39 hook scripts.

ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | wc -l
# 39

C2 — the manifest registers 28 distinct hooks.

grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u | wc -l
# 28

C3 — 11 shipped hooks are absent from the manifest.

comm -23 \
  <(ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | xargs -n1 basename | sort) \
  <(grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u)
# 11 lines

C4 — of those 11, two have no importer anywhere; they are unreachable by any path.

comm -23 \
  <(ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | xargs -n1 basename | sort) \
  <(grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u) \
| while read -r f; do b="${f%.hook.*}"
    grep -qlE "from \"\./$b\.hook\"" LifeOS/install/hooks/*.hook.ts 2>/dev/null || echo "UNWIRED: $f"
  done
# UNWIRED: DriftReminder.hook.ts
# UNWIRED: PostToolObserver.hook.ts

The other 8 resolve to PreToolGuard, StopGates, or MemoryTurnStart and are correctly dispatcher-loaded.

C5 — LoopDetector is dead transitively: its sole importer is PostToolObserver, which is itself unwired. C4's check misses this because it only tests one level; your reconciler does the transitive closure and catches it.

C6 — your own tool reports all three. Against an installed tree:

bun LIFEOS/TOOLS/Doctor.ts --reconcile
# { "unwired": ["LoopDetector.hook.ts", "DriftReminder.hook.ts", "PostToolObserver.hook.ts"], ... }

Output from a clean v7.1.1 install, today. This matches C4+C5 exactly.

C7 — Doctor.ts ships in the payload and is copied by the same install run.

ls LifeOS/install/LIFEOS/TOOLS/Doctor.ts

C8 — InstallHooks.ts never cross-checks the two inventories.
LifeOS/Tools/InstallHooks.tshookFiles is set at line 71 from the payload dir, hookFilesCopied at line 102 from the destination; added/skipped/events come from mergeHooks at line 80. The two are reported in the same JSON object (line 104) and never compared. No call to the reconciler appears in the file.

C9 — the shipped docs disagree with the shipped manifest. LifeOS/install/LIFEOS/DOCUMENTATION/Hooks/HookSystem.md:1562 states:

"the UserPromptSubmit chain is 6 hooks (PromptProcessing, SatisfactionCapture, ReminderRouter, DriftReminder, MemoryTurnStart, AlgorithmNudge)"

jq -r '.hooks.UserPromptSubmit[].hooks[].command' LifeOS/install/hooks/hooks.json
# 4 entries; DriftReminder and AlgorithmNudge absent

Same for HookSystem.md:322 (PostToolObserver on PostToolUse) and :22 (census of 30 registered; actual 28).

Why this wasn't caught

Not an oversight in the reconciler — it works. The gap is that --reconcile has exactly one automatic caller, LIFEOS/PULSE/modules/doctor.ts, which serves it to the Pulse System→Hooks page. Pulse is an à-la-carte component. On a Core install it never runs, so the reconciler's correct answer is never requested.

Impact

DriftReminder is named twice in LIFEOS_SYSTEM_PROMPT.md as the enforcement for voice/format drift, and HookSystem.md documents it as registered. It never executes. PostToolObserver is documented as the sync catch-all; loop/oscillation detection never executes.

Because these hooks never fire, they also produce no logs — so on a stock install their absence is silent. There is no error, no degraded mode, and no telemetry indicating anything is missing.

Suggested fix

One line in InstallHooks.ts: after --apply, shell Doctor.ts --reconcile and print unwired alongside the existing success report. The tool is already on disk at that point in the install — it was copied by the same run.

This would have surfaced these three hooks at install time, on every install, for every user, in the release that introduced the reconciler.

Happy to open the PR.

Reproduction

git clone https://github.com/danielmiessler/LifeOS && cd LifeOS
ls LifeOS/install/hooks/*.hook.ts LifeOS/install/hooks/*.hook.sh | wc -l   # 39
grep -oE '[A-Za-z]+\.hook\.(ts|sh)' LifeOS/install/hooks/hooks.json | sort -u | wc -l   # 28

Two numbers, one repo, no install required.

Scope

Verified against v7.1.1 on a clean install (second vanilla install; the first was archived to eliminate a legacy-content variable). C1–C5 and C7–C9 are properties of the repo tree and hold for any clone. C6 was run on my install; it depends on settings.json content and will differ on an install whose settings carry additional registrations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions