Summary
install/hooks/FormatGate.hook.ts shipped its observation-only fix on 2026-07-12, but an already-installed host keeps the previously-installed blocking copy at ~/.claude/hooks/FormatGate.hook.ts. Nothing in the upgrade path replaces the active hook, so the exact failure the fix was written to prevent (forced full re-emit, duplicated response) persists indefinitely on upgraded installs.
Evidence (LifeOS 7.1.1, Claude Code 2.1.217, WSL2 / Ubuntu)
| File |
mtime |
size |
behaviour |
~/.claude/hooks/FormatGate.hook.ts (active, registered via StopGates) |
2026-07-12 23:10 |
6993 B |
return { decision: "block", reason: blockReason(violation) } |
~/.claude/skills/LifeOS/install/hooks/FormatGate.hook.ts |
2026-07-14 18:34 |
7295 B |
appendObs({... decision: "flag" ...}); return null |
danielmiessler/LifeOS@main:LifeOS/install/hooks/FormatGate.hook.ts |
— |
7295 B |
byte-identical to the install copy |
StopGates.hook.ts was byte-identical to upstream on the same host, so this is a single stale file rather than a wholesale un-synced hooks directory.
From LIFEOS/MEMORY/OBSERVABILITY/format-gate.jsonl on that host: 36 decision:"block" records (20 too-many-emdashes, 16 no-banner), each one a forced re-emit of a full response for a cosmetic format issue. That is the symptom described in the upstream header comment:
OBSERVATION-ONLY (2026-07-12): this gate does NOT block. ... That re-emit prints the response twice, which is the duplicated-output failure [the principal] escalated on hard (blocked ~10x no-banner / ~14x em-dash in a single session before this change).
Secondary issue: the block reason contradicts the banner regex
While the stale copy is active, its guidance is also self-defeating. BANNER = /════\s*LifeOS/ requires the literal word LifeOS, but blockReason() quotes back:
Re-emit the response in the one LifeOS format — ════ LifeOS banner as the first line ...
A model reading that reasonably emits a bare ════════... rule, which fails the regex again. Observed three consecutive identical blocks from this before the regex was read directly. Upstream deleted blockReason() along with the blocking path, so this resolves itself once the sync issue is fixed — worth noting only because it makes the stale state much more expensive than it looks.
Suggested fix
Have the upgrade path (or Doctor --reconcile) diff active ~/.claude/hooks/*.ts against skills/LifeOS/install/hooks/*.ts and report or replace drifted files. Related in kind to #1510, where shipped hooks and registered hooks disagreed.
Workaround
cp ~/.claude/skills/LifeOS/install/hooks/FormatGate.hook.ts ~/.claude/hooks/FormatGate.hook.ts
Summary
install/hooks/FormatGate.hook.tsshipped its observation-only fix on 2026-07-12, but an already-installed host keeps the previously-installed blocking copy at~/.claude/hooks/FormatGate.hook.ts. Nothing in the upgrade path replaces the active hook, so the exact failure the fix was written to prevent (forced full re-emit, duplicated response) persists indefinitely on upgraded installs.Evidence (LifeOS 7.1.1, Claude Code 2.1.217, WSL2 / Ubuntu)
~/.claude/hooks/FormatGate.hook.ts(active, registered via StopGates)return { decision: "block", reason: blockReason(violation) }~/.claude/skills/LifeOS/install/hooks/FormatGate.hook.tsappendObs({... decision: "flag" ...}); return nulldanielmiessler/LifeOS@main:LifeOS/install/hooks/FormatGate.hook.tsStopGates.hook.tswas byte-identical to upstream on the same host, so this is a single stale file rather than a wholesale un-synced hooks directory.From
LIFEOS/MEMORY/OBSERVABILITY/format-gate.jsonlon that host: 36decision:"block"records (20too-many-emdashes, 16no-banner), each one a forced re-emit of a full response for a cosmetic format issue. That is the symptom described in the upstream header comment:Secondary issue: the block reason contradicts the banner regex
While the stale copy is active, its guidance is also self-defeating.
BANNER = /════\s*LifeOS/requires the literal wordLifeOS, butblockReason()quotes back:A model reading that reasonably emits a bare
════════...rule, which fails the regex again. Observed three consecutive identical blocks from this before the regex was read directly. Upstream deletedblockReason()along with the blocking path, so this resolves itself once the sync issue is fixed — worth noting only because it makes the stale state much more expensive than it looks.Suggested fix
Have the upgrade path (or
Doctor --reconcile) diff active~/.claude/hooks/*.tsagainstskills/LifeOS/install/hooks/*.tsand report or replace drifted files. Related in kind to #1510, where shipped hooks and registered hooks disagreed.Workaround