Skip to content

Relocated install (CLAUDE_CONFIG_DIR): launchd services and headless claude calls write to ~/.claude and create it #2812

Open

Description

@ronaldomt

Version

LifeOS 8.761.0 · attestation: 768 system file(s) differ from danielmiessler/[email protected]:

  • hooks/README.md
  • hooks/hooks.json
  • hooks/lib/subagent-git-sandbox-core.ts
  • hooks/lib/texting-guard-core.ts
  • skills/HyperFrames/Setup.md
  • skills/HyperFrames/SKILL.md
  • skills/HyperFrames/Intents/SystemFilm.md
  • skills/HyperFrames/Intents/LaunchVideo.md
  • skills/HyperFrames/Intents/GeneratedContentVideo.md
  • skills/HyperFrames/Intents/ContentToAnimation.md
  • skills/HyperFrames/Intents/CapabilityVideo.md
  • skills/Telos/Setup.md
  • skills/Telos/DashboardTemplate/app/api/chat/route.ts
  • skills/Telos/DashboardTemplate/README.md
  • skills/Telos/DashboardTemplate/lib/telos-data.ts
  • skills/Telos/SKILL.md
  • skills/Telos/Intents/WriteReport.md
  • skills/Telos/Intents/Update.md
  • skills/Research/Setup.md
  • skills/Research/SKILL.md
  • skills/Research/Intents/Retrieve.md
  • skills/Research/Intents/QuickResearch.md
  • skills/Research/Intents/StandardResearch.md
  • skills/Research/Intents/Fabric.md
  • skills/Research/Intents/DeepInvestigation.md
  • skills/Research/Intents/AnalyzeAiTrends.md
  • skills/Research/Intents/ExtractAlpha.md
  • skills/CrimeStats/DataSources.md
  • skills/CrimeStats/Setup.md
  • skills/CrimeStats/SKILL.md
  • skills/CrimeStats/Intents/CompareRegion.md
  • skills/CrimeStats/Intents/QuickStats.md
  • skills/CrimeStats/Intents/IncidentReport.md
  • skills/CrimeStats/Intents/TrendAnalysis.md
  • skills/Vitals/SKILL.md
  • skills/Vitals/Intents/DeepDiagnosis.md
  • skills/Vitals/Intents/FindCulprit.md
  • skills/Vitals/Intents/HealthCheck.md
  • skills/Order/Setup.md
  • skills/Order/SKILL.md
  • …and 728 more

What is broken

On an install placed with CLAUDE_CONFIG_DIR=~/.claude-vanilla (so ~/.claude stays free for a stock Claude Code), the Setup install succeeds everywhere except the core services, and those services then write into ~/.claude, creating it when it does not exist. Three causes stack. First, several service installers and launchd templates hardcode $HOME/.claude / __HOME__/.claude / {{HOME}}/.claude (PULSE/manage.sh, manage-deriver.sh, MenuBar/install.sh, com.lifeos.pulse.plist and com.lifeos.deriver.plist, and the TOOLS/*.plist.template files), so Services.ts install rendered an empty Pulse plist and a deriver that looked for its template under ~/.claude, and InstallAll failed with "core service install failed". Second, no launchd plist carries CLAUDE_CONFIG_DIR (Conduit's two plists are generated with only PATH), so any job whose code falls back to the default resolves ~/.claude: Conduit wrote its events to ~/.claude/LIFEOS/USER/CONDUIT/ even though its plist pointed at the right script. Third, Inference.ts (and CarrierProbe.ts, RELAY/bridge/session.ts, AgentJob.ts) spawn the claude CLI with a bare copy of process.env; under launchd that has no CLAUDE_CONFIG_DIR, so the hourly Conduit insight run started a Claude Code on the default root and created both ~/.claude/ (backups, plugins, projects, sessions) and ~/.claude.json. Separately, about 300 code lines (Pulse modules, Atlas, Socrates, Relay, Router, several skills) resolve join(HOME, ".claude", ...) directly, which the repo's own ConfigRootLiteral detector flags, and the docs and system prompt name ~/.claude about 4,000 times (for example "Read ~/.claude/LIFEOS/ALGORITHM/LATEST"), so a lifeos session on a relocated root is pointed at the wrong tree.

Where (file:line)

LIFEOS/PULSE/manage.sh:5; LIFEOS/TOOLS/Inference.ts:455; LIFEOS/PULSE/Conduit/InstallConduitInsight.ts:58

Repro on a clean tree

1. macOS, no `~/.claude` directory and no `~/.claude.json`. `export CLAUDE_CONFIG_DIR=~/.claude-vanilla` and launch Claude Code from that root.
2. Run the LifeOS Setup intent and accept Core: `bun Tools/InstallAll.ts --yes --json --components <defaults>`.
3. Observe: InstallAll exits 1, "core service install failed (exit 1)": `com.lifeos.pulse` fails `launchctl bootstrap` (its rendered plist in ~/Library/LaunchAgents is 0 bytes) and `com.lifeos.deriver` reports "plist template missing at ~/.claude/LIFEOS/PULSE/com.lifeos.deriver.plist". `~/.claude/LIFEOS/` now exists, holding Conduit events/state, menubar logs, worksweep and derived-sync logs.
4. Even with every path in the templates fixed, leave the services running for an hour: when `com.lifeos.conduit.insight` runs, `Inference.ts` spawns `claude -p` without `CLAUDE_CONFIG_DIR` and `~/.claude/` (backups, plugins, projects/-/<id>.jsonl, sessions) plus `~/.claude.json` appear, with `firstStartTime` equal to the run time.
5. The negative control above greps the shipped files (copies saved before any local change, since this install is now patched): the hardcoded `$HOME/.claude/LIFEOS` in manage.sh and the Pulse plist, the bare `{ ...process.env }` in Inference.ts, and the PATH-only EnvironmentVariables in InstallConduitInsight.ts.

Negative control

Probe: cd ~/.claude-vanilla/backups/lifeos-install-fix-20261114-093726/LIFEOS-before-path-fix && grep -nE 'HOME(__)?/\.claude/LIFEOS' PULSE/manage.sh PULSE/com.lifeos.pulse.plist && cd ~/.claude-vanilla/backups/vanilla-resolvers-2026-11-14T13-49-58-721Z/LIFEOS && grep -nE 'const env = \{ \.\.\.process\.env \};' TOOLS/Inference.ts && grep -nA3 '<key>EnvironmentVariables</key>' PULSE/Conduit/InstallConduitInsight.ts · exit 0 · captured 2026-11-14T14:04:44.591Z

Red looks like: PULSE_DIR="$HOME/.claude/LIFEOS/PULSE"

PULSE/manage.sh:5:PULSE_DIR="$HOME/.claude/LIFEOS/PULSE"
PULSE/com.lifeos.pulse.plist:15:    <string>__HOME__/.claude/LIFEOS/PULSE</string>
PULSE/com.lifeos.pulse.plist:28:    <string>__HOME__/.claude/LIFEOS/PULSE/logs/pulse-stdout.log</string>
PULSE/com.lifeos.pulse.plist:30:    <string>__HOME__/.claude/LIFEOS/PULSE/logs/pulse-stderr.log</string>
455:    const env = { ...process.env };
58:  <key>EnvironmentVariables</key>
59-  <dict>
60-    <key>PATH</key><string>${escapeXml(launchdPath())}</string>
61-  </dict>

Suggested fix

What fixed it on this install (applied locally as idempotent scripts; not a PR, since a real fix belongs in the generator and resolver, not in sed):

  • Bake the resolved root into every launchd job: render templates and the code-generated Conduit plists from getClaudeDir() instead of $HOME/.claude, and add CLAUDE_CONFIG_DIR, LIFEOS_DIR and LIFEOS_CONFIG_DIR to each plist's EnvironmentVariables (the workflows dispatcher already bakes some install-root variables; the others do not).
  • Pin CLAUDE_CONFIG_DIR on the child env wherever the claude CLI is spawned (Inference.ts, CarrierProbe.ts, RELAY/bridge/session.ts, AgentJob.ts), so a headless call can never open a different config root than the one LifeOS resolved.
  • lifeos.ts pinConfigRoot only exports CLAUDE_CONFIG_DIR when LIFEOS_SKILLS_DIR is set, so the lifeos / k aliases from the Helm zsh layer launch Claude Code on ~/.claude for a relocated install; pin it whenever the launcher resolved a root.
  • Drive the ConfigRootLiteral lane to zero outside the resolvers, and render ~/.claude in docs and the system prompt from the install's real root (RenderIdentity already rewrites placeholders; the root could be one more).
  • Related, prior: public issue Runtime tools hardcode ~/.claude, ignoring the config root the installer honors (56 of 161 TOOLS files) #1974 (config root resolved from a HOME literal).

Environment

  • OS: macOS 26.3 (arm64)
  • Harness: claude-code
  • bun: 1.3.12 · git: 2.50.1
  • Doctor broken: identity-placeholders

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