Summary
DeployCore.ts correctly honors a non-default config root (CLAUDE_CONFIG_DIR), but 56 of the 161 tools in LIFEOS/TOOLS/ resolve their paths from homedir() + ".claude" instead of the detected config root. The install succeeds; the installed system then reads and writes outside its own tree.
INSTALL.md §2 states the intent explicitly — "Every path below comes from this — don't assume ~/.claude or any single harness." The installer follows that rule. The runtime does not.
Reproduction
Verified on macOS 26.4.1, arm64, bun 1.3.14, LifeOS 7.40.4 (ce046f2):
export CLAUDE_CONFIG_DIR="$HOME/.lifeos"
cd <release>/LifeOS
bun Tools/DetectEnv.ts # configRoot: /Users/sabo/.lifeos ✓ correct
bun Tools/ScanConflicts.ts # clean, no collisions
bun Tools/DeployCore.ts --apply
DeployCore deploys correctly into ~/.lifeos (57 skills, full runtime), then fails on a post-install step:
GenerateKnowledgeSchemaDoc exited 1:
ENOENT: no such file or directory,
open '/Users/sabo/.claude/LIFEOS/MEMORY/KNOWLEDGE/_schema.md'
The tool was launched from /Users/sabo/.lifeos/LIFEOS/TOOLS/ and wrote to /Users/sabo/.claude/.
Source — LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts:24:
const OUT = pathResolve(homedir(), ".claude/LIFEOS/MEMORY/KNOWLEDGE/_schema.md");
Scope
Measured with grep over *.ts in ~/.lifeos/LIFEOS/TOOLS/ only. Hooks, skills, and the PULSE tree were not scanned — the real number is likely higher, not lower.
|
count |
.ts files in LIFEOS/TOOLS/ |
161 |
hardcode homedir() + .claude (5 syntactic variants) |
56 |
mention .claude anywhere (incl. strings, help text) |
131 |
read CLAUDE_CONFIG_DIR |
3 |
Of the hardcoding files, only 7 offer an env escape hatch (LIFEOS_DIR, CORTEX_MEMORY_ROOT), and nothing in the install sets those. Setting an env var is therefore not a workaround — it would cover 7 of 56.
Representative examples:
AgentWatchdog.ts:38 process.env.LIFEOS_DIR || join(homedir(), ".claude", "LIFEOS")
ActivityParser.ts:25 path.join(homedir(), ".claude")
CarrierProbe.ts:38 join(homedir(), ".claude")
Cortex.ts:283 ... ?? join(homedir(), ".claude/LIFEOS/MEMORY")
DoctrineReplay.ts:26 join(homedir(), ".claude/LIFEOS/MEMORY/WORK")
HealthSnapshot.ts:34 join(homedir(), ".claude/LIFEOS/USER/HEALTH/snapshots")
Why this matters beyond a failed post-install step
The visible symptom is one tool exiting 1. The invisible one is worse: on a machine where ~/.claude exists but belongs to something else, the failure is silent.
That is our case. This machine runs two separate Claude config trees — ~/.claude and a second one under a different config root. With LifeOS installed into the second tree, Cortex memory, WORK directories, health snapshots, and the knowledge schema would all resolve into the first tree. Nothing errors; the data simply lands in the wrong house and the two installations quietly interleave.
GenerateKnowledgeSchemaDoc failed loudly here only because ~/.claude/LIFEOS/ did not exist. Had it existed, the install would have reported success while writing into a directory it does not own.
Suggested fix
A single shared resolver used by every tool, seeded from the same detection the installer already performs — e.g. LIFEOS/TOOLS/lib/configRoot.ts exporting a configRoot() that reads CLAUDE_CONFIG_DIR (or a LIFEOS_DIR written at install time) and falls back to ~/.claude only when neither is set. The three tools that already read CLAUDE_CONFIG_DIR show the intended shape.
A deterministic gate would prevent regression, in the spirit of the 7.40.4 release notes ("every finding must also become a deterministic catch"): fail the cut if any file under LIFEOS/ contains homedir() joined to a literal .claude. That check is one grep and would have caught all 56.
Environment
- macOS 26.4.1 (arm64), bun 1.3.14, git 2.50.1
- LifeOS 7.40.4, commit
ce046f2
- Harness: Claude Code,
configRoot detected correctly as the non-default path
- Install path: additive, into an empty directory, no pre-existing LifeOS
Filed after a parallel install performed specifically to test upgrade safety from a pre-7.x tree in a non-default config root. Happy to test a patch against the same setup.
Summary
DeployCore.tscorrectly honors a non-default config root (CLAUDE_CONFIG_DIR), but 56 of the 161 tools inLIFEOS/TOOLS/resolve their paths fromhomedir() + ".claude"instead of the detected config root. The install succeeds; the installed system then reads and writes outside its own tree.INSTALL.md§2 states the intent explicitly — "Every path below comes from this — don't assume~/.claudeor any single harness." The installer follows that rule. The runtime does not.Reproduction
Verified on macOS 26.4.1, arm64, bun 1.3.14, LifeOS 7.40.4 (
ce046f2):DeployCoredeploys correctly into~/.lifeos(57 skills, full runtime), then fails on a post-install step:The tool was launched from
/Users/sabo/.lifeos/LIFEOS/TOOLS/and wrote to/Users/sabo/.claude/.Source —
LIFEOS/TOOLS/GenerateKnowledgeSchemaDoc.ts:24:Scope
Measured with
grepover*.tsin~/.lifeos/LIFEOS/TOOLS/only. Hooks, skills, and the PULSE tree were not scanned — the real number is likely higher, not lower..tsfiles inLIFEOS/TOOLS/homedir()+.claude(5 syntactic variants).claudeanywhere (incl. strings, help text)CLAUDE_CONFIG_DIROf the hardcoding files, only 7 offer an env escape hatch (
LIFEOS_DIR,CORTEX_MEMORY_ROOT), and nothing in the install sets those. Setting an env var is therefore not a workaround — it would cover 7 of 56.Representative examples:
Why this matters beyond a failed post-install step
The visible symptom is one tool exiting 1. The invisible one is worse: on a machine where
~/.claudeexists but belongs to something else, the failure is silent.That is our case. This machine runs two separate Claude config trees —
~/.claudeand a second one under a different config root. With LifeOS installed into the second tree, Cortex memory, WORK directories, health snapshots, and the knowledge schema would all resolve into the first tree. Nothing errors; the data simply lands in the wrong house and the two installations quietly interleave.GenerateKnowledgeSchemaDocfailed loudly here only because~/.claude/LIFEOS/did not exist. Had it existed, the install would have reported success while writing into a directory it does not own.Suggested fix
A single shared resolver used by every tool, seeded from the same detection the installer already performs — e.g.
LIFEOS/TOOLS/lib/configRoot.tsexporting aconfigRoot()that readsCLAUDE_CONFIG_DIR(or aLIFEOS_DIRwritten at install time) and falls back to~/.claudeonly when neither is set. The three tools that already readCLAUDE_CONFIG_DIRshow the intended shape.A deterministic gate would prevent regression, in the spirit of the 7.40.4 release notes ("every finding must also become a deterministic catch"): fail the cut if any file under
LIFEOS/containshomedir()joined to a literal.claude. That check is one grep and would have caught all 56.Environment
ce046f2configRootdetected correctly as the non-default pathFiled after a parallel install performed specifically to test upgrade safety from a pre-7.x tree in a non-default config root. Happy to test a patch against the same setup.