Skip to content

Four symlink-blind entry guards live outside scripts/, where #10086's class-closing gate cannot see them — three are the file://${argv[1]} template that also breaks on percent-encoding #10269

Description

@os-zhuang

Measured while implementing #10086 (PR for claude/issue-10086-invoked-directly-entry-guard). Not repaired there: that card's file surface is scripts/**, and the gate it lands (check:entry-guard) deliberately scans scripts/ only. Filed unassigned, severity for triage.

The measurement

#10086 removed every hand-typed invokedDirectly guard from scripts/ — 11 distinct spellings across 33 files — and closed the class with a gate that allows only scripts/invoked-as.mjs to read process.argv[1].

Sweeping the rest of the tree with the same predicate finds four more sites the gate's scope does not reach:

packages/objectql/scripts/dry-run-hash-compat.ts:261
process.argv[1] && /dry-run-hash-compat\.ts$/.test(process.argv[1])
packages/core/examples/kernel-features-example.ts:304
packages/core/examples/phase2-integration.ts:355
examples/embed-objectql/src/index.ts:60
import.meta.url === `file://${process.argv[1]}`

Both shapes are on the measured-broken list from #10086:

  • The template form (three sites) is spelling S11 in that card's census. It goes inert through a symlink, and it bypasses the encoder pathToFileURL applies, so it also goes inert on any checkout path containing a character needing percent-encoding — measured on a synthetic probe, a # in any parent directory name is enough, with no symlink involved. (A space is not enough: URL normalises that one. So the failure is sporadic in exactly the way that makes it hard to attribute.)
  • The regex-basename form (dry-run-hash-compat.ts) is the S3/S10 family: it survives a symlink that keeps the basename, but it answers true for any entry script whose path ends in that name, so it can fire on import as well as go inert.

Why it is not just "examples"

packages/objectql/scripts/dry-run-hash-compat.ts is a compatibility checker, not a demo — the failure direction there is the same silent one #10086 documents: reached the wrong way it does nothing, exits 0, and a caller holding the status reads a pass.

The three template-form sites are example/demo entry points, so the severity is genuinely lower — but they are also the files a reader copies from, which is how a spelling that was removed 33 times comes back.

Note on the sibling predicate

packages/cli/src/utils/invocation.ts already exports isProcessEntry, the symlink-correct predicate for package code, and its header cites #10086. So the fix for at least the packages/** sites likely needs no new code — it is isProcessEntry, or the example equivalent. What is missing is anything that makes them use it.

Two directions for whoever takes this, in ascending cost:

  1. point the four sites at an existing correct predicate;
  2. widen scripts/check-entry-guard.mjs's population beyond scripts/ — it already exports scanFile(rel, source, opts) and takes the roster from a directory walk, so the scope is one constant. The blocker is that packages/** cannot import scripts/invoked-as.mjs, so the gate would need to accept isProcessEntry as a second legal spelling — which is a real design question, not a mechanical widening, and is why The invokedDirectly entry guard is spelled ~8 ways across scripts/, and every one of them makes its script silently inert (exit 0, no output) when reached through a symlink — including check-governed-merges.mjs, the governed-surface register #10086 did not just do it.

Also worth correcting while someone is in that file: invocation.ts's header says #10086 measured the guard "in ~8 spellings". The census on origin/main at 923c424 is 11 distinct spellings across 33 files; ~8 was the estimate in the card body, not the measurement.

Dedupe

search_issues for "entry guard process.argv[1] symlink inert in packages and examples, isProcessEntry, file:// template literal guard outside scripts" returns 0 results. #10086 covers scripts/** only and its dispatch fenced the surface explicitly.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions