Pages nothing looked at
lifecycle/stray-playground-pages, #3727 · five untracked explainer pages found by hand in one day · SessionStart, async, age-thresholded · 2026-08-31
Three rules that together made the pages invisible
glyph writes docs/playgrounds/<category>/<slug>.html and never stages it .gitignore: playgrounds/ ignored + !docs/playgrounds/ un-ignored -> untracked AND un-ignored ci.yml: INERT includes docs/playgrounds/.* and docs/<type>--<slug>/.* -> no gate ever sees them result: `git status` shows "?? docs/playgrounds/dev/x.html" and nothing acts on it. #3706 recurred as #3727.
Why SessionStart, not Stop: at Stop the page the session just wrote is minutes old, so any age threshold is zero (fires on every page, including ones about to be committed) or meaningless. The page still untracked an hour later, seen by the NEXT session, is the forgotten one.
Try it: does the next session hear about the file?
What ships
| piece | detail |
|---|---|
| hook | lifecycle/stray-playground-pages, SessionStart, async, 5 s; count 175 to 176 |
| git call | git --no-optional-locks status --porcelain=v1 --untracked-files=all -z -- docs/playgrounds 'docs/*--*', 3 s bound, silent on any git failure |
| threshold | 1 h default, ORK_STRAY_PLAYGROUND_MIN_AGE_HOURS; opt-out ORK_NO_STRAY_PLAYGROUND_CHECK=1 |
| once per session | .claude/state/stray-playgrounds-<sid>.json, read back after writing, siblings older than 7 days unlinked |
| control | the test plants a backdated untracked page in a real temp git repo and asserts the SessionStart context names it; on main the hook does not exist, so the file fails |
| live proof | the two explainer pages sitting untracked in the primary tree since 08-30 are named by the next SessionStart after merge; after they land on a docs branch, the hook goes quiet |