Skip to content

substituteTree and checkSurvivingPlaceholders both skip extensionless files — 5 shipped Raycast patterns install with unrendered {{PRINCIPAL_FULL_NAME}} #2061

Description

@lockyer-tech

Summary

Five files in the v7.40.4 release payload carry an identity placeholder that is meant to be rendered, and neither substituteTree nor checkSurvivingPlaceholders can see them, because both gate on TEMPLATE_EXTENSIONS and these files have no extension.

They install with the literal token in place, and checkSurvivingPlaceholders reports passed: true.

This is distinct from the self-corruption family (#1813, #1852, #1874, #1993) — those are about substitution reaching a file it should not. This is the opposite: substitution cannot reach files it should, and the verifier shares the blind spot, so nothing reports it.

Affected files

All under install/skills/Fabric/Patterns/raycast/:

capture_thinkers_work
create_story_explanation
extract_primary_problem
extract_wisdom
yt

Each carries at line 14:

# @raycast.author {{PRINCIPAL_FULL_NAME}}
# @raycast.authorURL https://github.com/danielmiessler

Measured against the v7.40.4 payload: these are 5 of 5 extensionless token-bearing files, and the only extensionless token-bearing files in the payload — so the fix is bounded.

Cause

In InstallEngine.ts (v7.40.4):

  • TEMPLATE_EXTENSIONS has no entry for "".
  • substituteTree gates on it: if (!TEMPLATE_EXTENSIONS.has(fileExtension(filePath))) return;
  • checkSurvivingPlaceholders gates on the same set.

fileExtension() returns "" for a basename with no dot, and "" is not in the set, so the file is skipped by both functions identically. The verifier is blind in exactly the same place as the substituter, which is why the failure is silent rather than reported.

Worth noting

fileExtension()'s own doc comment cites this exact directory as the reason it was rewritten:

Was filePath.slice(filePath.lastIndexOf(".")), which searches the whole path: for ~/.claude/skills/Fabric/Patterns/raycast/yt that returns ".claude/skills/Fabric/Patterns/raycast/yt" — the dot in .claude. Harmless by accident (no such key in the set, so extension-less files were skipped) …

The comment observes that extensionless files are skipped and classifies it as harmless. Five of them need rendering. The computation of the extension was fixed; whether an extensionless file belongs in scope was never asked.

Reproduce

After the step 9(d) substitution on a fresh install:

grep -rn '{{PRINCIPAL_FULL_NAME}}' ~/.claude/skills/Fabric/Patterns/raycast/

Five hits. Then run checkSurvivingPlaceholders over the config root: passed: true.

Suggested fix

Two options; the second is the one I'd argue for.

  1. Add "" to TEMPLATE_EXTENSIONS. Smallest diff, but it puts every extensionless file in the payload into mutation scope — a wider blast radius than the bug.

  2. Split the two gates. Keep mutation extension-scoped — never rewrite a file whose type you did not recognise — and let checkSurvivingPlaceholders read every regular file. Detection is cheap and does not care what anyone thought of, and a verifier that shares the substituter's blind spot cannot report what the substituter missed.

Option 2 is what we run locally. On our install it moved the render set from 113 files / 265 occurrences to 118 / 270, and those five files are the entire difference. If option 1 is preferred, note that fileExtension() also returns "" for a pure dotfile (.env has its dot at index 0), so skills/Fabric/Patterns/.env would become mutable.

Related, possibly a separate issue

The same shared-blind-spot pattern applies to SKIP_DIRS, which contains MEMORY for both functions. On our install LIFEOS/MEMORY/STATE/capabilities.json carries three occurrences of {{PRINCIPAL_NAME}} — inside the Doctor's own diagnostic string reporting unrendered placeholders. The one file that names the defect is the one file the verifier cannot see. Happy to file that separately if useful.

Adjacent open issue: #2058 covers checkSurvivingPlaceholders passing on a zero-file scan, which is the same "the gate cannot fail" family from a different direction.


Found on v7.40.4 (be9e8ef889f00a29f4fd677dee4772fdf32e07ce) during a clean install on Debian 13.

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