Skip to content

Runtime tools hardcode ~/.claude, ignoring the config root the installer honors (56 of 161 TOOLS files) #1974

Description

@sabo961

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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