You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MergeSettings drops every installer hook once settings.user.json defines any hook #2466
settings.system.json deliberately ships without hooks; the installer writes them straight into settings.json, and mergeSettingsPreserving carries them forward on each regeneration. That carry-forward stops as soon as settings.user.json contains a hooks key at all. One user hook on one event, for example a single Stop command, replaces the whole installer hook set. SessionStart, LoadContext, SessionCleanup and the rest disappear from the regenerated settings.json, and no warning is printed. The code comment says a layer that defines the key wins. The result is that adding a single personal hook silently disables LifeOS's own hooks.
Where (file:line)
LIFEOS/TOOLS/MergeSettings.ts:162 lists hooks in INSTALLER_OWNED_KEYS.
LIFEOS/TOOLS/MergeSettings.ts:169-170: the existing value is copied only when neither layer defines the key, so any user hooks object wins whole.
LIFEOS/TOOLS/MergeSettings.ts:125: the only overlay guard in this path, guardPermissionOverlays, checks permissions, not hooks.
Repro on a clean tree
From the LifeOS/ directory of a v8.761.0 checkout:
bun -e 'import {mergeSettingsPreserving} from "./install/LIFEOS/TOOLS/MergeSettings.ts";const existing = {hooks:{SessionStart:[{hooks:[{type:"command",command:"LoadContext.hook.ts"}]}]}};const user = {hooks:{Stop:[{hooks:[{type:"command",command:"my-own.sh"}]}]}};console.log(JSON.stringify(mergeSettingsPreserving({}, user, existing).hooks))'
With an empty user layer ({} instead of user) the same call returns the installer hooks unchanged: {"SessionStart":[{"hooks":[{"type":"command","command":"LoadContext.hook.ts"}]}]}. Only the presence of a user hooks key drops them.
Suggested fix
Merge hooks per event instead of whole-object: keep the installer's entries from existing and append the user layer's entries for the same event, letting __remove delete a specific installer hook when that is wanted. At minimum, print a warning when a user hooks key shadows installer hooks that exist in the current settings.json. Untested.
Clarification on provenance: the repro above was first run on files byte-identical to the tag, not on a checkout. I have now re-run it on a fresh clone of v8.761.0 (fb9aef4, git status clean). Output is unchanged: with the user layer the result is {"Stop":[...my-own.sh...]} and SessionStart is gone; with an empty user layer the SessionStart hook is returned unchanged.
Activity
mj-deving commentedon Oct 30, 2026
Clarification on provenance: the repro above was first run on files byte-identical to the tag, not on a checkout. I have now re-run it on a fresh clone of
v8.761.0(fb9aef4,git statusclean). Output is unchanged: with the user layer the result is{"Stop":[...my-own.sh...]}andSessionStartis gone; with an empty user layer theSessionStarthook is returned unchanged.