Skip to content

fix: sweep remaining ~/.claude hardcodes across LIFEOS/TOOLS, PULSE, and skills (follow-up to #1429)#1431

Open
borfast wants to merge 9 commits into
danielmiessler:mainfrom
borfast:fix/claude-config-dir-tools-sweep
Open

fix: sweep remaining ~/.claude hardcodes across LIFEOS/TOOLS, PULSE, and skills (follow-up to #1429)#1431
borfast wants to merge 9 commits into
danielmiessler:mainfrom
borfast:fix/claude-config-dir-tools-sweep

Conversation

@borfast

@borfast borfast commented Jul 6, 2026

Copy link
Copy Markdown

Stacked on #1429 — this branch contains #1429's commits plus one sweep commit on top. Review just the last commit, or merge #1429 first and this rebases to the sweep alone.

What

#1429 fixed the always-on surface (installer, hooks, hook-invoked tools, launcher) for non-default CLAUDE_CONFIG_DIR profiles, and its description promised the rest as a follow-up. This is that follow-up: the same treatment extended to the on-demand tools — 152 files across LIFEOS/TOOLS, LIFEOS/PULSE, and skills/*/Tools.

  • Path resolution: every inline ~/.claude fallback (join/resolve with homedir()/HOME, process.env.LIFEOS_DIR || … patterns, ${HOME}/.claude/… template strings) now routes through LIFEOS/TOOLS/Paths.ts (getClaudeDir() / getLifeosDir()), matching the hooks' resolver.
  • Projects-dir encoding: ActivityParser and KnowledgeHarvester derive Claude Code's munged projects subdir name (-Users-x--claude) from the active config dir instead of hardcoding the default profile's encoding — so transcript/memory discovery works on any profile.
  • User-facing hints: usage blocks, .env key instructions, and audit output render the active profile's paths via displayPath() (byte-identical output on default installs).
  • Shell scripts: ${CLAUDE_CONFIG_DIR:-$HOME/.claude}.

Deliberately left as-is

  • Doc-notation parsers and scanner regexes that match the canonical ~/.claude spelling inside documentation text (DocCheck, ReferenceCheck, ArchitectureSummaryGenerator, the Daemon SecurityFilter redaction pattern) — they parse docs, not the filesystem.
  • The remote Arbol layout constant (ARBOL/.claude).
  • macOS-only Interceptor shell help text (heredoc quoting makes blind substitution risky; happy to follow up if wanted).
  • skills/Telos/DashboardTemplate — a self-contained Next.js app with its own build; excluded from the sweep.

Verification

borfast added 7 commits July 5, 2026 23:20
Claude Code supports multiple profiles via CLAUDE_CONFIG_DIR, but the
hooks payload assumed the config root is always ~/.claude, at three
levels:

- hooks.json ships every command as $HOME/.claude/hooks/... and
  InstallHooks.ts merged them verbatim, so on a non-default profile all
  60 hook entries pointed at files the installer never deployed there —
  every hook event errored.
- hooks/lib/paths.ts getClaudeDir() only honored CLAUDE_PLUGIN_ROOT,
  never CLAUDE_CONFIG_DIR.
- ~20 hook files bypassed lib/paths.ts with their own inline
  join(homedir(), '.claude', ...) spellings.

Fixes, in the spirit of the agentic installer (DetectEnv.ts already
detects CLAUDE_CONFIG_DIR correctly — the rest of the pipeline just
ignored what it found):

- InstallEngine.rewriteHooksConfigRoot(): retargets the default-root
  spellings ($HOME/${HOME}/~ + /.claude) in payload hook commands at
  the detected configRoot at merge time. No-op when configRoot IS
  ~/.claude, so default installs stay byte-identical.
- getClaudeDir() falls back CLAUDE_PLUGIN_ROOT → CLAUDE_CONFIG_DIR →
  ~/.claude; getLifeosDir() default now derives from getClaudeDir().
- All hook files with inline .claude paths now route through the
  lib/paths.ts helpers (getClaudeDir/getSkillsDir/getSettingsPath/
  paiPath).
- install.sh skills-dir detection probes CLAUDE_CONFIG_DIR before the
  ~/.claude and ~/.config/claude defaults.
- Nested skill-payload mirrors (install/skills/LifeOS/...) synced.

Verified: InstallHooks.ts --apply against a sandbox config root rewrites
all 60 commands (0 left on the default root), preserves pre-existing
foreign hook entries, and the rewrite is a no-op for a default-root
install.
Companion to the hooks fix: the tools that hooks invoke on every turn
(MemoryHealthCheck, FreshnessCache, SettingsBackport, MemoryReviewer,
ISARender) and the shared LifeosConfig loader still hardcoded ~/.claude,
so on a non-default CLAUDE_CONFIG_DIR profile they read from — and wrote
observability/state files into — the wrong profile.

Adds LIFEOS/TOOLS/Paths.ts mirroring hooks/lib/paths.ts resolution
(CLAUDE_PLUGIN_ROOT → CLAUDE_CONFIG_DIR → ~/.claude; LIFEOS_DIR override
for the data dir) and routes those tools' root constants through it.
Default installs resolve to the same paths as before.
…orts

Second sweep after runtime verification against a non-default
CLAUDE_CONFIG_DIR profile caught stragglers the first pass missed:

- 13 hooks + handlers/UpdateCounts.ts still resolved LIFEOS_DIR or the
  Claude root with inline 'process.env.LIFEOS_DIR || join(HOME,
  ".claude", ...)' / 'resolve(homedir(), ".claude")' fallbacks —
  they wrote MEMORY state/observability files into ~/.claude from a
  ~/.claude-gc profile. All now route through lib/paths.ts.
- lib/safety-classifier.ts TRUSTED_PREFIXES trusted the literal
  ~/.claude instead of the active config root.
- Five LIFEOS/TOOLS files (lifeos.ts launcher, algorithm.ts,
  TranscriptParser.ts, IntegrityMaintenance.ts, SessionHarvester.ts)
  imported hooks/lib via '../../../.claude/hooks/lib/...' — a relative
  path that climbs OUT of the config root and back in through a literal
  '.claude' segment. Now the structure-relative '../../hooks/lib/...',
  which resolves in any profile.

Verified: all 46 wired hooks execute from a ~/.claude-gc profile with
zero module-resolution or path failures, write state/observability only
under the active profile, and leave ~/.claude untouched.

Display-only strings that mention ~/.claude in user-facing messages are
left as-is.
The launcher hardcoded CLAUDE_DIR/BANNER_SCRIPT at ~/.claude, so on a
non-default CLAUDE_CONFIG_DIR profile it would cd into and read MCP
state from the wrong profile even though the spawned claude process
inherits the right env. WALLPAPER_DIR stays home-relative by design.
The always-on hooks' guidance messages (memory-health 'Run: bun
~/.claude/...', writing-gate PangramScore instructions, Art workflow
listing, system-file-guard import suggestion, work-repo setup hint)
hardcoded ~/.claude, so on a non-default CLAUDE_CONFIG_DIR profile they
told the user to run commands against the wrong profile — commands that
either fail or, worse, act on another install.

Adds displayPath() to hooks/lib/paths.ts (abbreviates the home prefix
to ~ for display) and builds those messages from the already-resolved
path constants. Default-root installs render byte-identical messages.
The message-correctness fix is building hints from resolved path
constants; displayPath() only abbreviates the home prefix so default
installs render byte-identical strings.
…and skills

Follow-up to the CLAUDE_CONFIG_DIR fix: extends the same treatment from
the always-on hooks surface to the on-demand tools — ~150 files across
LIFEOS/TOOLS, LIFEOS/PULSE, and skills/*/Tools.

- Path resolution: every inline '~/.claude' fallback (join/resolve with
  homedir()/HOME, 'process.env.LIFEOS_DIR ||' patterns, template
  strings) now routes through LIFEOS/TOOLS/Paths.ts.
- Claude Code projects-dir encoding (ActivityParser, KnowledgeHarvester)
  derives the munged '-Users-x--claude' segment from the active config
  dir instead of hardcoding the default profile's encoding.
- User-facing hints (usage blocks, .env key instructions, audit output)
  render the active profile's paths via displayPath().
- Shell scripts use ${CLAUDE_CONFIG_DIR:-$HOME/.claude}.

Left as-is, deliberately: doc-notation parsers and scanner regexes that
match the canonical '~/.claude' spelling inside documentation text
(DocCheck, ReferenceCheck, ArchitectureSummaryGenerator, the Daemon
SecurityFilter redaction pattern), the remote Arbol layout constant,
and macOS-only Interceptor shell help text.

Verified: bun build clean on all 299 swept files (75 pre-existing
failures from missing external deps are unchanged); zero functional
'.claude' literals remain outside the justified set.
borfast added 2 commits July 6, 2026 02:12
LIFEOS_StatusLine.sh was missed by the sweep (root-level LIFEOS file):
CLAUDE_HOME hardcoded ~/.claude, and LIFEOS_DIR consumed the settings
env value as-is — but the harness passes env values UNexpanded, so
'$HOME/.claude/LIFEOS' arrived literally and every cache write failed.
Now honours CLAUDE_CONFIG_DIR and expands $HOME/${HOME}/~ prefixes.
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