Repository navigation
Description
Problem
The root ci script is not what runs in CI, and the name actively works against us.
ci: validate && coverage && verify:build-gate && verify:bundle-externals
&& smoke && smoke:web:firefox && ci:storybook
The smokes themselves do run in GitHub CI, and should — the workflow runs npm run smoke, which covers smoke:launcher, smoke:cli, smoke:tui, smoke:web and smoke:web:chromium. That is not in question here.
What must never run in CI is narrower: the non-Chromium engine passes. smoke:web:firefox (#2086) is deliberately absent, and smoke:web:webkit fails two of the three smokes outright. Confidence in the cross-engine runs is not high enough to let them break CI.
smoke:tui is a third, separate case and is not in that category: it is invoked in CI via smoke and self-skips there (process.env.CI), because the Ink TUI needs a real TTY. It handles itself and needs no guarding.
GitHub CI runs validate, coverage, the two verify gates, smoke and test:storybook — never the root ci script itself.
Three distinct problems follow from the name:
1. It collides with a built-in npm command. npm ci is clean-install-from-lockfile. It does not run the ci script — verified. So npm ci and npm run ci are one keystroke apart, do entirely different things, and the wrong one fails by succeeding at something else: several minutes of reinstalling node_modules instead of running the gate, with no error to tell you.
2. It invites the cross-engine passes into CI by accident. This is the one that matters. A future contributor editing .github/workflows/main.yml who sees a script called ci has every reason to think it belongs there — or to "fix" a workflow by changing npm ci to npm run ci. Either would silently pull smoke:web:firefox into GitHub CI. The maintainer position is that the cross-engine passes are always the local pre-push gate, period — confidence in them is not high enough to let them break CI, and smoke:web:webkit fails two of the three outright.
3. It muddles the concept every time it is documented. Six review rounds on #2133 kept turning up prose that blurred "GitHub CI" and "the local gate". A script literally named ci for the local-only thing guarantees that keeps happening.
Scope
Widened from a pure rename to repo-hygiene process work, since it touches ~40 references across 15 files and the concepts they describe.
1. Rename the gate, with no alias
ci → local:gate (suggestions welcome, but it should say local).
No back-compat alias. Deliberate: an alias preserves exactly the association being scrubbed, and leaves something copy-pasteable for a workflow author. Removing it outright means npm run ci fails with Missing script: ci and npm prints the available scripts — a good failure that teaches the right name.
prepush:gate. npm pre/post hooks match the exact script name, so prepush:gate is the pre-hook of push:gate and fires automatically if anyone ever adds one — verified. Plain prepush is safe (it does not hook push:gate), but local:gate avoids the class entirely and reads as the counterpart to GitHub CI.
Rename ci:storybook too — it is also local-only (the workflow calls npm run test:storybook from clients/web directly, not this wrapper). Something like local:storybook or fold it into the gate.
Commit history and older PR bodies will reference npm run ci. That is fine; history is a record of what was true then.
2. Guard that the gate can never drift into CI
The rename removes the invitation; a check removes the possibility. Add a test:scripts assertion over .github/workflows/** that fails on:
- an invocation of the gate itself (
local:gate); - an invocation of a non-Chromium engine pass (
smoke:web:firefox,smoke:web:webkit); - an invocation of
smoke:web:engine, whose engine comes from the environment and so cannot be read off the workflow at all; - setting
SMOKE_BROWSERto anything other thanchromium— the back door that would redirect an otherwise innocentnpm run smoke.
It must NOT forbid npm run smoke, smoke:web:chromium, or smoke:tui. Those belong in CI and are there today; smoke:tui self-skips under process.env.CI on its own. The guard is about engines, not about smokes.
This is the durable half. Everything else here is prose that can rot; this cannot.
3. Document the two-tier model in one place
Right now the CI-vs-local split is described in README.md, AGENTS.md, .github/copilot-instructions.md, three client READMEs, and several script headers — which is why it keeps drifting. One canonical table (what runs in GitHub CI, what runs only locally, and why each local-only step is local-only), with the others pointing at it.
Per the mirror rule, AGENTS.md and .github/copilot-instructions.md change in the same PR.
4. Sweep the stale references
~40 across README.md, AGENTS.md, .github/copilot-instructions.md, clients/{web,cli}/README.md, docs/inspector-roadmap-2026-h2.md, and header comments in scripts/*.mjs + one test.
Acceptance criteria
- No root script is named
ciorci:*;npm run cifails with npm's missing-script error. - A
test:scriptscheck fails if any workflow file invokes the gate, a non-Chromium engine pass, orsmoke:web:engine, or setsSMOKE_BROWSERto a non-Chromium value — while continuing to allownpm run smokeandsmoke:web:chromium, which belong in CI. - Every doc that names the gate uses the new name and distinguishes GitHub CI from it.
AGENTS.mdand.github/copilot-instructions.mdupdated together.- No change to what any gate actually runs — this is naming, guarding and documentation.
Notes
Came out of #2133, where smoke:web:firefox was deliberately placed in the local gate rather than GitHub CI after a trialled CI job was removed for never disagreeing with Chromium. That decision is only as durable as the thing stopping someone from undoing it by accident, which today is nothing but a comment.
Activity
cliffhall commentedon Aug 26, 2026
Triage: Priority Medium (total 6)
npm ci≠npm run ciis a workaroundNot scored higher despite the "must never run in CI" framing: the guard in scope item 2 is what makes that durable, and until someone actually edits the workflow, nothing is currently wrong.
cliffhall commentedon Aug 26, 2026
Correction — the scope of the guard was wrong as first written. Body updated.
@cliffhall: "I do want smoke tests to run in CI (they do now, right?) just with chromium only. It's the firefox and safari ones I'm not certain of."
Right on both counts, and I had conflated three different things into one list.
Yes — the smokes run in GitHub CI today. The workflow runs
npm run smoke, which issmoke:launcher && smoke:cli && smoke:tui && smoke:web && smoke:web:chromium. Chromium only. That is the intended state and nothing here should change it.The three categories, kept separate now:
smoke:launcher,smoke:cli,smoke:web,smoke:web:chromiumsmoke:tuismoke, self-skips there (needs a real TTY)smoke:web:firefox,smoke:web:webkitMy first draft listed
smoke:tuialongside the engine passes. That was wrong: it is already in CI and no-ops there by design, so forbidding it would both misdescribe the status quo and block someone legitimately touching it later.The guard is about engines, not smokes. It now fails on: invoking
local:gate; invokingsmoke:web:firefox/smoke:web:webkit; invokingsmoke:web:engine(env-driven, so the engine cannot be read off the workflow at all); or settingSMOKE_BROWSERto a non-Chromium value — that last one being the back door that would redirect an otherwise innocentnpm run smoke. It explicitly must not forbidnpm run smoke,smoke:web:chromium, orsmoke:tui.Acceptance criteria updated to match.
chore(scripts): treat an expression-built script name as forbidden too (
chore(scripts): scan only executable positions, and match on the expr…
chore(scripts): parse the workflow instead of matching lines (#2146)
chore(scripts): walk the workflow schema's executable paths, and reso…
chore(scripts): make the browser rule kind-aware, cover container.env…
chore(scripts): detect a SMOKE_BROWSER assignment in any shell dialect (
chore(scripts): read a shell-expanded name as unreadable, and env key…
chore(scripts): stop the assignment capture at the closing quote (#2146)
chore(scripts): accept a dynamic SetEnvironmentVariable argument as a…
chore(scripts): scan the shell command template, and flag a multi-lin…
chore(scripts): cover PowerShell's Env: drive, and correct two stale …
Merge pull request #2167 from modelcontextprotocol/v2/chore/2146-rena…
chore: address Copilot review round 10
fix(scripts): make smoke:tui assert the TUI survives its first frame (m…
chore(scripts): rename the local pre-push gate off `ci`, and guard it…