Summary
Two related problem clusters surfaced while (re)installing LifeOS 6.0.5 on Linux (Parrot/Debian, Claude Code harness). One is a data-corrupting installer bug; the other is Pulse not being operational on Linux. Both are fixed on the affected machine; this issue documents root causes and proposes upstream fixes.
Problem 1 — Installer performs a blind HOME → /home/<user> substitution that corrupts config and source
Symptoms
settings.json contained "$/home/<user>/.claude/hooks/…" for every hook command, the statusline, and env vars (LIFEOS_DIR, LIFEOS_CONFIG_DIR, PROJECTS_DIR). A path beginning $/home/... is invalid (the shell can't expand $/), so all hooks and the statusline silently failed to execute.
- Three
permissions.deny safety rules were defanged: Bash(rm -rf $HOME) had become Bash(rm -rf $/home/<user>), which no longer matches a real rm -rf $HOME.
- 146 TypeScript source files under
LIFEOS/ + hooks/ were corrupted inside code, e.g. MemoryHealthCheck.ts:
const /home/<user> = process.env./home/<user> || ""; // was: const HOME = process.env.HOME
→ hard syntax errors; the tools wouldn't parse or run.
Root cause
A substitution pass replaced the bare token HOME with the literal home path ($HOME → $/home/<user>, const HOME → const /home/<user>) — the classic sed 's|HOME|/home/<user>|g' failure mode. The enabling weakness is still present in Tools/InstallEngine.ts:
TEMPLATE_EXTENSIONS includes .ts, .json, .sh, .toml, so substituteTree() rewrites source and settings.
substituteTree() substitutes via naive after.split(placeholder).join(value) for any key — so a caller passing a bare "HOME" placeholder rewrites every HOME substring across code.
Fix applied
Hardened substituteTree() to only substitute properly-delimited {{TOKEN}} placeholders:
if (!/^\{\{[A-Z0-9_]+\}\}$/.test(placeholder)) continue;
Verified: passing {HOME: "/home/<user>"} now leaves const HOME/$HOME untouched while {{LIFEOS_VERSION}} still resolves. Also repaired the live tree (settings.json $/home/<user> → $HOME; re-deployed the 146 corrupted source files from the pristine install/ payload).
Proposed upstream fix
- Ship the
substituteTree() braced-token guard above.
- Consider dropping
.ts from TEMPLATE_EXTENSIONS (source files shouldn't be templated).
Problem 2 — Pulse is not operational on Linux
INSTALL.md/setup treat Pulse as a macOS-launchd component; on Linux it neither auto-starts nor fully runs. Specific gaps:
- No Linux service — Pulse is only wired via
launchd (com.lifeos.pulse.plist). Nothing starts it on Linux, and it doesn't survive reboot.
- Dashboard + all APIs 404 —
PULSE/node_modules is missing yaml, so Observability/observability.ts fails to import → observabilityModule is null → every page and /api/* route 404s while /healthz can still read files. yaml is imported by observability but absent from PULSE/package.json.
settings.system.json shipped without a hooks block, yet MemoryHealthCheck.ts treats it as the hook source of truth and flags the memory hooks (MemoryReviewTrigger/Fire, MemoryHealthGate) as a "regression source" (they exist only in the generated settings.json). InstallHooks writes hooks into the generated settings.json, which MergeSettings regenerates — an architectural mismatch between "InstallHooks owns hooks" and "settings.system.json is the source of truth."
assistant-tasks cron fails forever — PULSE.toml enables a job running bun run Assistant/checks/tasks.ts, but the Assistant/ module isn't in the payload → perpetual failures → persistent degraded health.
LIFEOS_CONFIG_DIR semantics conflict — install tools (ScaffoldUser/LinkUser/SeedPulse) treat it as the data home whose /USER is the tree (~/.config/LIFEOS), while runtime tools (Grok.ts, YouTubeApi.ts, llcli.ts) treat it as the dir containing .env. The two only reconcile if .env and USER/ share a dir.
- Standalone install tools depend on unset env — running
ScaffoldUser/LinkUser directly (as the AI-install doc instructs) with LIFEOS_CONFIG_DIR unset/misset caused them to write into a literal $-named directory.
Fix applied (Linux)
bun add yaml in PULSE/ → observability module loads; /, /agents, /api/algorithm now serve 200.
- Built the Next.js dashboard export (
cd PULSE/Observability && bun install && bun run build).
- Disabled the
assistant-tasks job in PULSE.toml and cleared its stale consecutiveFailures from PULSE/state/state.json → /healthz = ok.
- Synced the full clean hook set into
settings.system.json and set LIFEOS_CONFIG_DIR = $HOME/.config/LIFEOS (matches the real data home).
- Added a systemd user service (
~/.config/systemd/user/lifeos-pulse.service) + loginctl enable-linger → Pulse runs on boot and restarts on failure. Verified ok across systemctl --user restart.
Proposed upstream fix
- Add
yaml to PULSE/package.json dependencies.
- Ship
settings.system.json with the system hooks block (or point MemoryHealthCheck at the real source), reconciling the InstallHooks/MergeSettings ownership model.
- Guard optional cron jobs (e.g.
assistant-tasks) when their module isn't present, instead of failing every tick.
- Ship a systemd unit template for Linux alongside the launchd plists, and have setup install+enable it on Linux (with
enable-linger).
- Document/standardize
LIFEOS_CONFIG_DIR semantics (data-home vs .env-home).
- Make
ScaffoldUser/LinkUser resolve their paths robustly (or fail loudly) when env vars are unset in standalone runs, rather than writing to a literal $ dir.
Environment
- OS: Linux (Parrot Security 7 / Debian-based), x64
- Harness: Claude Code; bun 1.3.10
- LifeOS: 6.0.5
- Config root:
~/.claude; data home: ~/.config/LIFEOS
Investigated and remediated via an assisted reinstall session; happy to open PRs for the substituteTree guard, the yaml dependency, and the systemd unit.
Summary
Two related problem clusters surfaced while (re)installing LifeOS 6.0.5 on Linux (Parrot/Debian, Claude Code harness). One is a data-corrupting installer bug; the other is Pulse not being operational on Linux. Both are fixed on the affected machine; this issue documents root causes and proposes upstream fixes.
Problem 1 — Installer performs a blind
HOME→/home/<user>substitution that corrupts config and sourceSymptoms
settings.jsoncontained"$/home/<user>/.claude/hooks/…"for every hook command, the statusline, andenvvars (LIFEOS_DIR,LIFEOS_CONFIG_DIR,PROJECTS_DIR). A path beginning$/home/...is invalid (the shell can't expand$/), so all hooks and the statusline silently failed to execute.permissions.denysafety rules were defanged:Bash(rm -rf $HOME)had becomeBash(rm -rf $/home/<user>), which no longer matches a realrm -rf $HOME.LIFEOS/+hooks/were corrupted inside code, e.g.MemoryHealthCheck.ts:Root cause
A substitution pass replaced the bare token
HOMEwith the literal home path ($HOME→$/home/<user>,const HOME→const /home/<user>) — the classicsed 's|HOME|/home/<user>|g'failure mode. The enabling weakness is still present inTools/InstallEngine.ts:TEMPLATE_EXTENSIONSincludes.ts,.json,.sh,.toml, sosubstituteTree()rewrites source and settings.substituteTree()substitutes via naiveafter.split(placeholder).join(value)for any key — so a caller passing a bare"HOME"placeholder rewrites everyHOMEsubstring across code.Fix applied
Hardened
substituteTree()to only substitute properly-delimited{{TOKEN}}placeholders:Verified: passing
{HOME: "/home/<user>"}now leavesconst HOME/$HOMEuntouched while{{LIFEOS_VERSION}}still resolves. Also repaired the live tree (settings.json$/home/<user>→$HOME; re-deployed the 146 corrupted source files from the pristineinstall/payload).Proposed upstream fix
substituteTree()braced-token guard above..tsfromTEMPLATE_EXTENSIONS(source files shouldn't be templated).Problem 2 — Pulse is not operational on Linux
INSTALL.md/setup treat Pulse as a macOS-launchd component; on Linux it neither auto-starts nor fully runs. Specific gaps:launchd(com.lifeos.pulse.plist). Nothing starts it on Linux, and it doesn't survive reboot.PULSE/node_modulesis missingyaml, soObservability/observability.tsfails to import →observabilityModuleis null → every page and/api/*route 404s while/healthzcan still read files.yamlis imported by observability but absent fromPULSE/package.json.settings.system.jsonshipped without a hooks block, yetMemoryHealthCheck.tstreats it as the hook source of truth and flags the memory hooks (MemoryReviewTrigger/Fire,MemoryHealthGate) as a "regression source" (they exist only in the generatedsettings.json).InstallHookswrites hooks into the generatedsettings.json, whichMergeSettingsregenerates — an architectural mismatch between "InstallHooks owns hooks" and "settings.system.json is the source of truth."assistant-taskscron fails forever —PULSE.tomlenables a job runningbun run Assistant/checks/tasks.ts, but theAssistant/module isn't in the payload → perpetual failures → persistentdegradedhealth.LIFEOS_CONFIG_DIRsemantics conflict — install tools (ScaffoldUser/LinkUser/SeedPulse) treat it as the data home whose/USERis the tree (~/.config/LIFEOS), while runtime tools (Grok.ts,YouTubeApi.ts,llcli.ts) treat it as the dir containing.env. The two only reconcile if.envandUSER/share a dir.ScaffoldUser/LinkUserdirectly (as the AI-install doc instructs) withLIFEOS_CONFIG_DIRunset/misset caused them to write into a literal$-named directory.Fix applied (Linux)
bun add yamlinPULSE/→ observability module loads;/,/agents,/api/algorithmnow serve 200.cd PULSE/Observability && bun install && bun run build).assistant-tasksjob inPULSE.tomland cleared its staleconsecutiveFailuresfromPULSE/state/state.json→/healthz=ok.settings.system.jsonand setLIFEOS_CONFIG_DIR=$HOME/.config/LIFEOS(matches the real data home).~/.config/systemd/user/lifeos-pulse.service) +loginctl enable-linger→ Pulse runs on boot and restarts on failure. Verifiedokacrosssystemctl --user restart.Proposed upstream fix
yamltoPULSE/package.jsondependencies.settings.system.jsonwith the system hooks block (or pointMemoryHealthCheckat the real source), reconciling the InstallHooks/MergeSettings ownership model.assistant-tasks) when their module isn't present, instead of failing every tick.enable-linger).LIFEOS_CONFIG_DIRsemantics (data-home vs.env-home).ScaffoldUser/LinkUserresolve their paths robustly (or fail loudly) when env vars are unset in standalone runs, rather than writing to a literal$dir.Environment
~/.claude; data home:~/.config/LIFEOSInvestigated and remediated via an assisted reinstall session; happy to open PRs for the
substituteTreeguard, theyamldependency, and the systemd unit.