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.
-
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.
-
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.
Summary
Five files in the v7.40.4 release payload carry an identity placeholder that is meant to be rendered, and neither
substituteTreenorcheckSurvivingPlaceholderscan see them, because both gate onTEMPLATE_EXTENSIONSand these files have no extension.They install with the literal token in place, and
checkSurvivingPlaceholdersreportspassed: 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/:Each carries at line 14:
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_EXTENSIONShas no entry for"".substituteTreegates on it:if (!TEMPLATE_EXTENSIONS.has(fileExtension(filePath))) return;checkSurvivingPlaceholdersgates 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: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:
Five hits. Then run
checkSurvivingPlaceholdersover the config root:passed: true.Suggested fix
Two options; the second is the one I'd argue for.
Add
""toTEMPLATE_EXTENSIONS. Smallest diff, but it puts every extensionless file in the payload into mutation scope — a wider blast radius than the bug.Split the two gates. Keep mutation extension-scoped — never rewrite a file whose type you did not recognise — and let
checkSurvivingPlaceholdersread 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 (.envhas its dot at index 0), soskills/Fabric/Patterns/.envwould become mutable.Related, possibly a separate issue
The same shared-blind-spot pattern applies to
SKIP_DIRS, which containsMEMORYfor both functions. On our installLIFEOS/MEMORY/STATE/capabilities.jsoncarries 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
checkSurvivingPlaceholderspassing 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.