Skip to content

Skills library: 20 lowercase skills/art|telos|evals references against PascalCase dirs — ENOENT on every Linux install, invisible on APFS #1677

Description

@christauff

Summary

The shipped skills library references three skill directories in lowercase while the directories themselves are PascalCase:

Reference in the tree Directory that actually ships
~/.claude/skills/art/… skills/Art/
~/.claude/skills/telos/dashboard-template skills/Telos/DashboardTemplate
~/.claude/skills/evals/ skills/Evals/

On macOS with the default case-insensitive APFS volume, all of these resolve and everything works. On Linux — and on a case-sensitive APFS volume, which is a supported macOS configuration — they resolve to nothing and the command exits ENOENT. There is no way to see this from a case-insensitive dev box, which is why I wanted to bring the exact inventory rather than just the symptom.

Verified at d1d6240 (main, 2026-07-23) against a read-only clone, cross-checking every lowercase reference against the real directory name on disk.

The user-visible consequence: 12 of the Art skill's 22 workflows have a generation command that fails on Linux. Art documents its generation step as a copy-pasteable bun run line; 12 workflow files spell that line with skills/art/ and 7 spell it correctly, so the failure is per-workflow rather than skill-wide.

Impact: this is in the install payload

Worth flagging because it changes the blast radius. Upstream's skills library lives at LifeOS/install/skills/, and DeployCore.ts copies that directory into <configRoot>/skills/:

// LifeOS/Tools/DeployCore.ts:56
/** (a) skills library: install/skills/* → configRoot/skills/ (one copyMissing). */

Every occurrence below is inside that payload. So these aren't stale paths in an old tree that a long-lived install carries forward — they are shipped fresh to every new Linux installation, at first bun Tools/DeployCore.ts --apply.

Exact inventory — 20 occurrences, 20 files (upstream d1d6240)

All paths relative to LifeOS/install/skills/.

Runnable commands — 15

These are commands a user copies and executes; each one fails outright on a case-sensitive FS.

File Line Text
Art/Workflows/AnnotatedScreenshots.md 264 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Aphorisms.md 242 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Comics.md 313 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Comparisons.md 288 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Frameworks.md 279 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Maps.md 332 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Mermaid.md 649 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/RecipeCards.md 293 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Stats.md 271 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Taxonomies.md 271 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Timelines.md 266 bun run ~/.claude/skills/art/Tools/Generate.ts \
Art/Workflows/Visualize.md 598 bun run ~/.claude/skills/art/Tools/Generate.ts \
Sales/Workflows/CreateSalesPackage.md 160 bun run ~/.claude/skills/art/Tools/Generate.ts \
Sales/Workflows/CreateVisual.md 109 bun run ~/.claude/skills/art/Tools/Generate.ts \
Telos/DashboardTemplate/README.md 33 cp -r ~/.claude/skills/telos/dashboard-template /path/to/your/project

Note that Sales inherits the failure: both of its visual workflows shell out to Art's generator through the lowercase path.

The Telos one is wrong in both segments — telosTelos and dashboard-templateDashboardTemplate. It fails on Linux, and on macOS it fails too (the hyphenated name doesn't exist under any casing), so that line is the "Copy the Template" first step of the DashboardTemplate README and currently cannot succeed anywhere.

Documentation references — 5

Wrong on a case-sensitive FS but not directly executed:

File Line Text
Art/Lib/discord-bot.ts 8 * @see ~/.claude/skills/art/SKILL.md
Art/Lib/midjourney-client.ts 8 * @see ~/.claude/skills/art/SKILL.md
Art/Tools/GenerateMidjourneyImage.ts 19 * @see ~/.claude/skills/art/SKILL.md
Art/Tools/Generate.ts 23 * @see ~/.claude/skills/art/README.md
Evals/PROJECT.md 736 ~/.claude/skills/evals/ (file-structure block header)

Small extra on Art/Tools/Generate.ts:23 — besides the casing, Art/README.md doesn't exist in the shipped tree at all, so that @see has no target under any casing. Art/SKILL.md is presumably the intended one.

Why this looks like drift rather than convention

The same tree contains 14 correctly-cased ~/.claude/skills/Art/Tools/Generate.ts references. Within Art/Workflows/ alone, 7 workflow files spell it Art and 12 spell it art — sibling files whose command blocks are otherwise near-identical:

# Art/Workflows/Essay.md:620          — resolves everywhere
bun run ~/.claude/skills/Art/Tools/Generate.ts \
  --workflow=Essay \

# Art/Workflows/Frameworks.md:279     — ENOENT on Linux
bun run ~/.claude/skills/art/Tools/Generate.ts \
  --workflow=Frameworks \

So the intended convention is already PascalCase throughout; these 20 are just the ones a case-insensitive filesystem never surfaced. This is genuinely the easiest bug in the world to ship — nothing in the dev loop can fail on it.

Reproduce

On any Linux box (or a case-sensitive macOS volume), after a normal install:

bun run ~/.claude/skills/art/Tools/Generate.ts --help
# error: Module not found "/home/<user>/.claude/skills/art/Tools/Generate.ts"

Detection — one line, runnable from the repo root

This finds references that resolve only when case is ignored — i.e. exactly this bug class, with no noise from unrelated missing-file issues:

cd LifeOS/install/skills && rg -oIN '~/\.claude/skills/[A-Za-z0-9_./-]+' . \
  | sed 's|~/\.claude/skills/||' | sort -u \
  | while read -r p; do [ -e "$p" ] || find . -ipath "./${p%/}" -print \
      | sed "s|^|CASE MISMATCH: $p -> |"; done

Output at d1d6240:

CASE MISMATCH: art/SKILL.md -> ./Art/SKILL.md
CASE MISMATCH: art/Tools/Generate.ts -> ./Art/Tools/Generate.ts
CASE MISMATCH: evals/ -> ./Evals
CASE MISMATCH: Telos/ReportTemplate/public/fonts/ -> ./Telos/ReportTemplate/public/Fonts

(telos/dashboard-template doesn't appear here only because the hyphenation differs too, so -ipath can't match it either.)

One adjacent hit the detector turned up — direction unclear, your call

Same class, deeper in the tree, so noting it here rather than opening a second issue — Telos/Workflows/WriteReport.md refers to public/fonts/ three times (lines 303, 334, 612) but the shipped directory is Telos/ReportTemplate/public/Fonts. Line 612 is a runnable command:

cp ~/Projects/[your-site]/public/fonts/*.woff2 ~/.claude/skills/Telos/ReportTemplate/public/fonts/

On Linux the destination doesn't exist and cp fails. Total across both groups: 23 occurrences.

Unlike the 20 above, I'm not sure which way you'd want this one fixed. #1619 references commit bc0a20a ("purge case-duplicate template paths"), which as I read it made lowercase canonical inside the template tree — so renaming the shipped Fonts directory to fonts may be more consistent with where the templates are heading than correcting the three references upward. Flagging the mismatch rather than assuming a direction.

Suggested fix direction

Two parts, the second being the one that actually keeps it fixed:

  1. Mechanical correction — the 20 above are a pure find/replace, and the surrounding text needs no changes:

    ~/.claude/skills/art/      → ~/.claude/skills/Art/
    ~/.claude/skills/evals/    → ~/.claude/skills/Evals/
    ~/.claude/skills/telos/dashboard-template → ~/.claude/skills/Telos/DashboardTemplate
    

    Plus Art/Tools/Generate.ts:23's @see retargeted to Art/SKILL.md. The public/fonts mismatch is separate, since its direction is yours to pick per the note above.

  2. A guard, so the next one is caught on macOS too. The detection one-liner above is cheap enough to run in CI or as a release-time check over the payload — it needs no Linux runner, because it compares the reference to the on-disk name rather than relying on the filesystem to fail. LIFEOS/TOOLS/ReferenceCheck.ts already walks the shipped payload looking for broken references and would be the natural home for a case-strict comparison; today it can't catch these, since on a case-insensitive volume the path it checks does exist.

Optionally, Art/Tools/Generate.ts could emit a friendlier hint when argv[1] contains /skills/art/, but the CI check is the durable fix.

Why a guard and not just the find/replace — this class keeps recurring

The reason I'd argue for the CI check in part 2 rather than only the 20-line fix is that this class has come back repeatedly, and each previous fix was correct for the scope it was given. Every one of these addressed the runtime LIFEOS/ tree; the skills library simply wasn't in frame, so these 20 survived each pass:

A case-strict comparison would have caught all of them at once, on a case-insensitive dev box, without a Linux runner.

I checked the open issues (#1619, #1586, #1592, #1590, #1611, #1600, #1669, and PR #1633) and searched case, Linux, path, ENOENT, lowercase, and skills/art across all states — nothing open covers the skills-library surface.

Environment

  • Upstream main @ d1d6240, read-only clone
  • Linux (ext4, case-sensitive); the same failure occurs on a case-sensitive APFS volume
  • LifeOS 7.1.1

I'm filing this as a report rather than a PR, since the README notes the public repo is generated from a private source tree. If a patch would be easier to port than a description, I'd send the 20-line correction plus the case check folded into ReferenceCheck.ts — happy to do that on request.

Thanks for how quickly this project moves; the Linux-side reports are only findable because the tree is otherwise consistent enough that the exceptions stand out.

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