fix: sweep remaining ~/.claude hardcodes across LIFEOS/TOOLS, PULSE, and skills (follow-up to #1429)#1431
Open
borfast wants to merge 9 commits into
Open
Conversation
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
#1429 fixed the always-on surface (installer, hooks, hook-invoked tools, launcher) for non-default
CLAUDE_CONFIG_DIRprofiles, 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 acrossLIFEOS/TOOLS,LIFEOS/PULSE, andskills/*/Tools.~/.claudefallback (join/resolvewithhomedir()/HOME,process.env.LIFEOS_DIR || …patterns,${HOME}/.claude/…template strings) now routes throughLIFEOS/TOOLS/Paths.ts(getClaudeDir()/getLifeosDir()), matching the hooks' resolver.ActivityParserandKnowledgeHarvesterderive 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..envkey instructions, and audit output render the active profile's paths viadisplayPath()(byte-identical output on default installs).${CLAUDE_CONFIG_DIR:-$HOME/.claude}.Deliberately left as-is
~/.claudespelling inside documentation text (DocCheck,ReferenceCheck,ArchitectureSummaryGenerator, the DaemonSecurityFilterredaction pattern) — they parse docs, not the filesystem.ARBOL/.claude).skills/Telos/DashboardTemplate— a self-contained Next.js app with its own build; excluded from the sweep.Verification
bun buildclean on all 299 swept TypeScript files. 75 files failed to build before the sweep (missing external deps likeyaml,smol-toml— expected outside a deployed install with Bun auto-install) and the exact same 75 fail after: zero new failures..claudeliterals remain outside the justified list above.~/.claude-gcprofile), most of these tools need API keys, macOS, or a running Pulse server — this sweep is build-verified and pattern-reviewed, which is why it's separate from fix: honour CLAUDE_CONFIG_DIR — hooks and always-on tools work on non-default config roots #1429 rather than part of it.