run-nx-checks · v1.9 · 2026-10-21 · sha256 804eebbb3226b6a4
run-nx-checks v1.9A
Immutable. This exact content never changes and is served at /api/v1/blob/804eebbb3226b6a4.
---
name: run-nx-checks
description: Run nx format, lint, typecheck, test, and build on affected or specified projects, then fix unambiguous failures
context: fork
allowed-tools: Bash, Read, Edit, Write, Grep, Glob
license: MIT
metadata:
version: "1.9"
---
# Run Nx Checks
Run format, lint, typecheck, test, build. Fix unambiguous failures. Report anything judgment-laden
and every file you changed.
## Arguments
`$ARGUMENTS` — optional, space-separated: `[cpuCount] [projectName] [--remote-cache]`. Number
token → `cpuCount`. Non-number, non-flag token → `projectName`. `--remote-cache` flag → opt back
into the remote cache by dropping the remote-cache-off prefix. Default `cpuCount` = cores - 4
(min 1); count cores with `node -p 'os.cpus().length'`.
Default project scope = affected. Default remote-cache state = off (see Steps).
`$base`, `$cpuCount` and `$projectName` in the commands below are placeholders: substitute their
values (a fresh shell per Bash tool call holds none of them).
## Workspace
Run nx commands from wherever `nx.json` lives in this repo (commonly the root; in some repos a
subdirectory).
## Setup (before step 1)
Keep the Nx daemon warm so the project graph is reused across targets:
```bash
NX_DAEMON=true npx nx daemon --start >/dev/null 2>&1 || true
```
Snapshot the dirty files, so the final report can name every file the run changes (`hash-object`
without `-w` writes nothing):
```bash
git ls-files -z -mo --exclude-standard | while IFS= read -r -d '' f; do echo "$(git hash-object "$f" 2>/dev/null) $f"; done > "${TMPDIR:-/tmp}/nx-checks-before.txt"
```
### Affected base
`nx affected` and `format:write` diff against the *local* default branch, which is often stale or
missing. `$projectName` replaces only the `affected` commands, so resolve the base in both modes.
`NX_BASE` set: skip this section and omit `--base` from every command below; Nx reads it and a
deliberate env setting wins. Otherwise resolve the remote-tracking ref once and substitute the
printed ref for `$base` in every `affected`, `format:write` and `show projects --affected` command
below:
```bash
base=$(node -p 'const j=require("./nx.json"); j.defaultBase ?? j.affected?.defaultBase ?? "main"' 2>/dev/null || echo main)
base=${base#remotes/}; [[ $base == */* ]] || base="origin/$base"
GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND='ssh -o BatchMode=yes -o ConnectTimeout=15' git fetch --quiet "${base%%/*}" "${base#*/}" || echo "fetch failed: base may be stale"
git rev-parse --verify --quiet "$base" >/dev/null && echo "base=$base" || echo "no such ref: omit --base"
```
- Omit `--base` when the ref is missing (Nx falls back to `defaultBase`).
- **Never pass `--head`.** With both flags Nx diffs two commits and ignores uncommitted and
untracked files, so an agent's unstaged edits vanish and every check passes vacuously.
- A failed fetch is not a failure: continue on the current ref, flag "base may be stale" in the
final report, and never retry outside the sandbox for it.
Why: [affected-base-rationale.md](affected-base-rationale.md).
## Fix rule
- Apply only mechanical/unambiguous fixes: lint auto-fix output, missing imports/types, obvious type
errors, test expectations that mirror a clear code change. An assertion or snapshot update is
mechanical only when the failing expectation names a value the working-tree diff changed on
purpose; edit that expectation by hand. Never pass `-u`/`--updateSnapshot` to a target.
- Never guess on anything judgment-laden: test failure that could be a real bug vs. an outdated
assertion, errors pointing at unrelated areas, pre-existing failures unrelated to recent work,
anything where multiple plausible fixes exist. Running forked, you can't ask mid-run: leave such
failures unfixed and carry them, with your analysis, into the final report.
- Keep changes minimal and scoped to the failure. No drive-by refactors.
- After fixing, re-run the same step command; the local cache replays the untouched projects.
Repeat until clean or only judgment-laden failures remain.
## Affected scope — sanity-check before step 1
Affected runs only (no `$projectName`); they can balloon. Before step 1, check scope with the
graph-only `npx nx show projects --affected --base=$base` (fast, no build):
- **Sandbox `.env` false positives.** The sandbox denies reading `**/.env`; `nx affected` hashes
changed files, so an unreadable `.env` reads as *changed* and marks its project affected. Repos
with `.env`-bearing apps (e.g. `apps/*-api`) thus drag those into every affected run and build
them for nothing. Tell-tale: run `git status`; it prints `<path>/.env: Operation not permitted`
for exactly those projects. Fix: add `--exclude=<their project names>` to every `affected`
command (`format:write` takes none) and list them as excluded in the final report, naming any
real edits they hold. Never re-run them outside the sandbox yourself; the user can, to include
them.
- **Large fan-out.** Large = more than half of `npx nx show projects | wc -l` or more than 20
projects, whichever is lower, counting affected projects after the `.env` exclusions (e.g. a
widely-shared lib change rippling across the workspace). Don't build the world unprompted: skip
steps 3–5 and report the project count and list in the final report so the user can re-run with
`$projectName`.
## Steps
**Scope is mandatory — never narrow it yourself.** With no `$projectName` argument, *every* target
runs at full scope — `nx affected` for lint/typecheck/test/build, whole-changeset `format:write`.
The single-project forms in the steps below apply **only** when the user explicitly passed
`$projectName`. Never substitute `nx run <proj>:<target>`, `nx <target> <changedLib>`, or
file-scoped `format --files` for the affected sweep: that skips every other affected project —
exactly where a shared-lib change regresses (a dependent project whose tests import the changed
lib).
Always prefix each task-running nx command (steps 1–5 and the flaky retry; `daemon` and
`show projects` run no tasks) with the remote-cache-off env vars inline:
`NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache`.
`NX_SKIP_REMOTE_CACHE` turns off every remote cache on Nx 20+; the other two cover Nx 19 and
Powerpack. Unknown vars are no-ops, so the prefix is safe whatever remote-cache backend (if any)
the repo uses.
The invariant: **remote cache always off (unless the user passed `--remote-cache`), local cache
always on** — always disable, never autodetect (a passing shell-side auth check doesn't predict
the in-process credential chain). Why: [remote-cache-rationale.md](remote-cache-rationale.md).
If the user explicitly passed `--remote-cache`, drop the prefix. Expect pipeline hits on `build`
only: `--fix` and `--maxWorkers=1` are hashed overrides, so lint and test entries never match a
pipeline run without them — keep the flags regardless.
**Always keep the local cache on.** The env vars above disable only the _remote_ read-through
cache; the local Nx cache must stay enabled so unchanged targets are replayed instead of re-run.
Do **not** add `--skip-nx-cache` (nor `NX_SKIP_NX_CACHE=true`) to any command — it bypasses the
local cache too, making every run slower for no benefit. The only valid reason to pass
`--skip-nx-cache` is a specific, stated need to bypass the local cache — e.g. investigating a
failure you suspect is caused by a stale cache entry. In that case, scope it to the single command
under investigation and say why; never use it as the default.
Write the env vars literally at the start of each such command, exactly as in the steps below. Do
**not** rely on `export` — each Bash tool call is a fresh shell, so exports do not carry across
calls (true for any nx env var). Never stash the prefix in a shell variable
(`NX_OFF="…"; $NX_OFF npx nx …` fails with "command not found": expanded variables are not parsed
as assignments).
1. Lint — `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx affected -t lint --base=$base --parallel=$cpuCount --fix`
(or `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx lint $projectName --fix`).
2. Format — `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx format:write --base=$base`
(both modes).
3. Typecheck — `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx affected -t typecheck --base=$base --parallel=$cpuCount`
(or `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx typecheck $projectName`).
Without the target, `affected` runs nothing and exits 0 and the single-project form errors with
"cannot find configuration": report typecheck as n/a, never as failing.
4. Test — `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx affected -t test --base=$base --parallel=$cpuCount --maxWorkers=1`
(or `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx test $projectName`).
5. Build — `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx affected -t build --base=$base --configuration=production --parallel=$cpuCount`
(or `NX_SKIP_REMOTE_CACHE=true NX_NO_CLOUD=true NX_POWERPACK_CACHE_MODE=no-cache npx nx build $projectName --configuration=production --parallel=$cpuCount`).
Apply the fix rule on any failure in steps 1–5 (a test failure: retry once first, see Flaky
tests). A failure left unfixed never stops the run: carry on with the next step.
### Flags per target
Why `--configuration=production` and `--maxWorkers=1`: [flags-rationale.md](flags-rationale.md).
- **`--maxWorkers=1` on the test step only.** Forwarding it to lint (ESLint) or build (esbuild)
fails as an unknown option.
- **Affected/run-many only, not single-project.** With one project there's no project-level
fan-out, so capping to one worker just serializes it — let a single project use all cores.
- Same reason, `--parallel` is dropped from single-project **lint**, **typecheck** and **test**
(one task each — no fan-out). It stays on single-project **build**, because `build` has
`dependsOn: ["^build"]`, so building one project fans out across its whole dependency chain.
## Flaky tests — retry once to classify
A failed `test` target may be flaky, not a real break. On a test failure, re-run that one target
once in step 4's single-project form, prefixed like the steps (`… npx nx test <project>`). If it
then passes (or, with Nx Cloud on, Nx prints `NX detected a flaky task`), it's flaky — don't try
to "fix" it; record it as a flaky `project:target` for the report. If it fails the same way again,
treat it as real under the Fix rule.
## Final report (mandatory)
This skill runs in a forked context, so its final message is the only channel back to the caller.
End with:
- the affected base used: the resolved ref, `NX_BASE` and its value, or omitted because the ref is
missing; flag a failed fetch as possibly stale;
- per target (lint / format / typecheck / test / build): clean, n/a, skipped (with the reason —
for large fan-out, the project count and list; for `.env` false positives, the excluded
projects), or each failing `project:target` with its cause, plus any flaky `project:target`;
- every file the run changed: hand-fixed (from your own edits — file plus one line on what and
why) and tool-applied (lint `--fix`, `format:write` — every other file that differs or is new
when the Setup snapshot command is re-run into `${TMPDIR:-/tmp}/nx-checks-after.txt` and the
two files are diffed).
Never report a bare "checks passed": an unlisted failure, skipped target or unmentioned edit reads
as green and the caller can't act on it.