danielmiessler / danielmiessler/LifeOS

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

Open
#2,061 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
19k
Forks
2.5k
Avg merge
8d 17h
Merged PRs (30d)
1

Description

## 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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in InstallEngine.ts by tracing the TEMPLATE_EXTENSIONS gates in substituteTree and checkSurvivingPlaceholders, then reproduce the issue with grep against the five named extensionless Raycast pattern files. Verify that the chosen scope renders all five {{PRINCIPAL_FULL_NAME}} tokens and that the verifier no longer reports passed while tokens remain.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.