v1.108.273 — A pattern that names two extensions and matches neither
Two things, both about the same mistake: a pattern that names two extensions and matches neither.
The regression (#445)
v1.108.271 fixed #435 for the nuxt and nestjs profiles by rewriting their entry_point_patterns as brace alternation ({ts,js}). The consumer is fnmatch, which expands no braces — fnmatch.translate("src/main.{ts,js}") yields (?s:src/main\.\{ts,js\})\Z, requiring a filename that literally contains {ts,js}.
The change did not add JavaScript coverage. It removed the TypeScript coverage that was working.
profile file v1.108.270 v1.108.271
nestjs src/main.ts True False
nestjs src/app.module.ts True False
nuxt plugins/a/b.ts True False
nuxt middleware/x/g.ts True False
nestjs is the worst case: its entire entry set was those two patterns, so a stock NestJS project went from two entry points to zero — and NestJS is TypeScript-first by convention, so this was the common path. The failure direction is the one that matters here: a lost reachability seed does not error, it silently reports a genuine framework root as unreachable.
Why the guard passed, which is the more useful half
The #435 sweep checks which extensions appear in a pattern string, and skipped brace patterns outright (if "{" in glob: continue) as already-covered. A pattern naming both ts and js satisfied a spelling check while matching no file on disk.
A test that inspects the shape of a fix cannot tell it from a plausible-looking non-fix. The new tests run patterns against realistic scaffold paths through _matches_any_pattern — the real matcher both dead-code tools share — so they measure effect. One of them asserts a non-entry file is still not an entry point, because every other test here would also pass if the patterns had been replaced with *.
A second fnmatch surprise, pre-existing
**/ translates to (?>.*?/) and therefore requires a slash: plugins/**/*.ts matches plugins/a/b.ts but not plugins/auth.ts. Every profile using **/ was missing files sitting directly in the named directory — the commonest layout there is. Fixing braces alone would have left the patterns still missing the majority case.
Both constraints now live in _entry_globs / _entry_named, which emit the flat and nested form per extension, so a profile lists where its entry points live and never how to spell a glob.
Explicit alternatives were chosen over teaching the matcher braces: that leaves the semantics of a user-facing parameter unchanged under the 1.x no-removal contract. Whether entry_point_patterns should also accept braces is filed separately as #446, along with the finding that a no-match today is silent in find_dead_code and only conditionally reported in get_dead_code_v2.
#435 closed
The next profile gained its JS/JSX counterparts — Next.js's own extension sets, js/jsx/ts/tsx for pages and layouts and js/ts for route handlers and middleware, across both the root and src/ layouts.
It had been deferred behind PR #433 so as not to force a conflict onto a contributor's rebase. That merged 2026-08-11. It landed here rather than on its own because doing it first would have propagated the brace defect to a third profile.
The ratchet added in v1.108.271 is what forced the fix and the retirement of its exemption to land together, and it worked exactly as designed.
Verification
tests/test_v1_108_273.py (11), 6 fail against the shipped v1.108.272 profiles; the 5 passing on both sides pin the fnmatch premises and the new helpers.
Full suite 7720 passed / 8 skipped / 0 failed, and 7714 / 14 / 0 under a clean 3.13 environment (7728 collected both ways); ruff check src/ clean; all 9 CI jobs green.