fix(skills): resolve customization lookup via CLAUDE_CONFIG_DIR, not hardcoded ~/.claude - #1924
Open
neilinger wants to merge 1 commit into
Open
Conversation
…hardcoded ~/.claude
Every affected skill's "Customization" block hard-codes
~/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/<Name>/ as a literal path in
the prose the model reads. On any install where CLAUDE_CONFIG_DIR points
somewhere other than ~/.claude, that path does not exist, and the skill's
own documented fallback ("if the directory does not exist, proceed with
skill defaults") turns the wrong path into a silently ignored
customization — no error, just quietly-dropped user config.
Claude Code does not substitute shell variables into a skill body — only
`!`cmd`` dynamic-context injections are expanded before the model sees the
text. This replaces the static path line with a `find`-based injection
that resolves at invocation time, guarded to fall back to the documented
default ($HOME/.claude) when CLAUDE_CONFIG_DIR is unset, so a default-dir
install behaves exactly as before. `find`, not a glob, because the
harness evaluates injections in zsh, whose default `nomatch` makes an
unmatched glob a hard error at expansion time (the common case is an
empty customization directory). The command always exits 0, since a
non-zero exit here would abort the whole skill invocation.
Scope: the 36 skills whose Customization block used the exact canonical
wording ("**Before executing, check for user customizations at:**" +
standalone path line). ContextSearch was in that set but has since been
removed from this tree, leaving 35 files changed here. Skills with
differently-worded customization blocks (WorldThreatModel, CMUX,
Webdesign) are not touched — the wording differs enough that a safe
mechanical fix isn't possible without rewriting skill-specific prose,
which is out of scope for this change.
Also worth a maintainer's attention, not fixed here: Telos's
`Workflows/Update.md` already uses this same `!`cmd`` injection syntax
(e.g. line 81, 105, 108), but that file is reached through Telos/SKILL.md's
"Workflow Routing" table and read on demand rather than loaded at
skill-invocation time — the injection there does not appear to be live.
Same bug family, different manifestation.
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
Every affected skill's
## Customizationblock hard-codes~/.claude/LIFEOS/USER/CUSTOMIZATIONS/SKILLS/<Name>/as a literal path inthe prose the model reads before running the skill. On any install where
CLAUDE_CONFIG_DIRpoints somewhere other than~/.claude, that pathdoesn't exist — and the skill's own documented fallback ("if the
directory does not exist, proceed with skill defaults") turns the wrong
path into a silently ignored customization. No error, the user's
config is just quietly dropped.
Why this form
Claude Code does not substitute shell variables into a skill body — only
!`cmd`dynamic-context injections are expanded before the modelsees the text. This PR replaces the static path line with a
find-basedinjection that resolves at invocation time, falling back to the
documented default (
$HOME/.claude) whenCLAUDE_CONFIG_DIRis unset —so a default-config-dir install behaves exactly as it did before this
change.
Two details that matter and were found the hard way:
find, not a glob. The harness evaluates these injections in zsh,whose default
nomatchturns an unmatched glob into a hard expansion-timeerror — and an empty customization directory is the common case, so a
glob here breaks the skill body on nearly every invocation.
entire skill invocation, so the command ends in
; true.Scope
35 skills whose
## Customizationblock used the exact canonical wording(
**Before executing, check for user customizations at:**+ a standalonepath line). A 36th skill matching that wording,
ContextSearch, has sincebeen removed from this tree, so it's not part of this diff.
Not included:
WorldThreatModel,CMUX, andWebdesignuse differentlyworded customization blocks (different phrasing / embedded prose rather
than the standalone canonical line), so a safe mechanical fix isn't
possible without hand-editing skill-specific text — left for a follow-up.
One more thing worth your attention
Telos'sWorkflows/Update.mdalready uses this same!`cmd`injection syntax (e.g. lines 81, 105, 108) — so the mechanism is already
an established idiom in this codebase. But that file is reached through
Telos/SKILL.md's "Workflow Routing" table and read on demand rather thanloaded at skill-invocation time the way
SKILL.mditself is, so theinjection there doesn't appear to actually be live. Same bug family
(hardcoded-path-adjacent), different manifestation — flagging it here
since I didn't want to scope-creep this PR into fixing it too.
Behavior check
Verified in both bash and zsh, with
CLAUDE_CONFIG_DIRunset (falls backto
$HOME/.claude, matches current behavior) and set to a directory thatdoes/doesn't contain a matching customization (correctly lists the file
or emits nothing) — exits 0 in every case.