Skip to content

fix(settings): expand $HOME/${HOME}/~ in env values during settings m…#1422

Open
ChernyshovSergiy wants to merge 1 commit into
danielmiessler:mainfrom
ChernyshovSergiy:fix/expand-home-in-env-merge
Open

fix(settings): expand $HOME/${HOME}/~ in env values during settings m…#1422
ChernyshovSergiy wants to merge 1 commit into
danielmiessler:mainfrom
ChernyshovSergiy:fix/expand-home-in-env-merge

Conversation

@ChernyshovSergiy

Copy link
Copy Markdown

Problem

settings.system.json ships env values that use a literal $HOME, e.g.:

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

The Claude Code harness shell-expands $HOME in hook command fields (they run through a shell), but injects env values verbatim. So process.env.LIFEOS_DIR
ends up as the literal string "$HOME/.claude/LIFEOS" — a non-absolute path.

Any tool/hook that resolves a path from that env var and writes to it (memory state, observability gates, freshness cache, learning captures) then writes to a path that
resolves against the current working directory. In practice this creates a stray directory literally named $HOME/ inside whatever project the session is running in:

  <project>/$HOME/.claude/LIFEOS/MEMORY/...
  <project>/$HOME/.claude/LIFEOS/USER/CACHE/freshness.json

— silently littering user project roots, and (worse) redirecting memory/learning writes away from the real ~/.claude tree.

Root cause

The merge stage (MergeSettings.ts) copies env values through untouched, and the harness never expands $HOME in env. Hardcoding an absolute path into settings.system.json
is not an option — the template intentionally uses $HOME everywhere (including all command fields) so it stays portable across installs and home directories.

Fix

Expand home references in settings.env values at merge time, keeping the template portable:

  • New expandHomeReference(value, home) — resolves a leading ~/~/, ${HOME}, and bare $HOME (token-bounded, so $HOMER is left alone). Non-path values (e.g.
    http://localhost:...) are untouched.
  • New expandEnvHomeReferences(settings, home = os.homedir()) — walks settings.env string values in place.
  • Wired into runCli after the merge, so a user override (which wins and is typically already absolute) is seen as-is; only remaining $HOME-style system defaults expand.

Scoped to env only — command fields keep $HOME literal so the harness's own shell expansion continues to make them portable. Pure addition (no lines removed). Idempotent:
values that are already absolute pass through unchanged.

Verification

  expandHomeReference("~/x",        "/H") -> "/H/x"
  expandHomeReference("$HOME/a",    "/H") -> "/H/a"
  expandHomeReference("${HOME}/b",  "/H") -> "/H/b"
  expandHomeReference("$HOMER",     "/H") -> "$HOMER"        # token-bounded, untouched
  expandHomeReference("http://x",   "/H") -> "http://x"      # non-path, untouched

Merging settings.system.json with an empty user overlay (no override) now yields absolute LIFEOS_DIR / LIFEOS_CONFIG_DIR / PROJECTS_DIR with no literal $HOME
remaining. The existing --check invariant (merge(system, user) === settings.json) holds after regeneration.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant