Skip to content

settings.system.json env values ship a literal, unexpanded $HOME — breaks USER scaffold on fresh installs (regression of the #1124 / #1270 class) #1404

Description

@rpriven

What happens

The shipped LifeOS/install/settings.system.json env block sets path variables as literal strings:

"env": {
    "LIFEOS_DIR": "$HOME/.claude/LIFEOS",
    "PROJECTS_DIR": "$HOME/Projects",
    "LIFEOS_CONFIG_DIR": "$HOME/.claude/LIFEOS",
    ...

Claude Code injects settings.json env values verbatim — it never shell-expands them. Verified directly: after setup, a fresh authed session's printenv LIFEOS_CONFIG_DIR returns the literal string $HOME/.claude/LIFEOS.

On a clean Debian 13 install (Claude Code 2.1.198, bun 1.3.14), during /LifeOS setup:

  • ScaffoldUser wrote the 93-file USER tree into a junk directory literally named $HOME/ under the skill folder
  • LinkUser created a broken, self-referential symlink ~/.claude/LIFEOS/USER → $HOME/.claude/LIFEOS/USER

The setup agent caught it mid-run. From its own transcript:

"Root cause found: the env var LIFEOS_CONFIG_DIR=$HOME/.claude/LIFEOS is set to a literal, unexpanded string. The bun tools don't expand $HOME, so:

  • ScaffoldUser wrote 93 files into a junk dir literally named $HOME/ under the skill folder
  • LinkUser created a broken symlink: ~/.claude/LIFEOS/USER → $HOME/.claude/LIFEOS/USER (also self-referential once expanded — it points back into configRoot, breaking the separation contract)"

It then repaired itself (moved USER into place, removed the junk tree) — genuinely impressive agentic recovery, but every fresh install is currently performing self-surgery. And the deeper issue: anything else that reads LIFEOS_CONFIG_DIR/LIFEOS_DIR from env at runtime (hooks, Pulse, TOOLS) receives the literal string and will mis-resolve unless it defensively expands.

Repro

Clean Linux box → bootstrap installer → /LifeOS setup → observe the $HOME/ junk dir under the skill folder + broken symlink. Or simply, post-setup: claude -p 'printenv LIFEOS_CONFIG_DIR'.

Scope note: a second same-day install on a machine with a full pre-existing config did NOT reproduce the junk-dir failure — USER landed correctly. The literal env value is constant (verified via printenv on both runs), but whether the TS tools mis-resolve appears environment-dependent. The clean-box repro is deterministic, so: fresh installs affected.

Prior art — this is a known class in this repo

The v6 payload reintroduces the same class in the new LIFEOS tree.

Suggested fix (either/both)

  1. Belt — tools resolve env paths defensively: value.replace(/^\$HOME\b/, os.homedir()) in a shared resolvePath() used by ScaffoldUser / LinkUser / DetectEnv.
  2. Suspenders — InstallEngine substitutes the real home into the deployed settings.system.json env block during the settings merge. This fixes ALL runtime consumers, not just the TS tools.

Evidence available: full setup-session transcript (JSONL) including the setup agent's own root-cause narrative, plus before/after tree listings — happy to share trimmed excerpts.

Found during day-one Linux (Debian) runtime validation of v6.

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