Skip to content

fix(ci): prune 6 stale eslint suppressions that made npm run lint exit 2 (#15159 G-05) - #15283

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.52from
jonlwheat2-gif:fix/g05-eslint-suppressions-pruned
Oct 2, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.52from
jonlwheat2-gif:fix/g05-eslint-suppressions-pruned

Conversation

@jonlwheat2-gif

Copy link
Copy Markdown
Contributor

Problem

npm run lint exits 2 on every PR:

There are suppressions left that do not occur anymore. To resolve this, re-run
the command with `--prune-suppressions` to remove unused suppressions.

ESLint fails an entire run when the suppressions file carries entries whose
violation no longer occurs. So this is not cosmetic debt — it is a red lint job
on the whole queue, and the lint job gates several others.

Fix

Six entries were stale. All six are @typescript-eslint/no-unused-vars:

File Count
open-sse/translator/request/openai-to-cursor.ts 3
src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx 1
src/app/api/v1/vscode/[token]/combos/route.ts 1
src/app/api/v1/vscode/raw/[token]/combos/route.ts 1
src/lib/oauth/providers/ghe-copilot.ts 1
tests/unit/cursor-agent-session.test.ts —

1094 → 1088 suppressed violations. The diff is exactly those 6 removals plus one
trailing comma; nothing else in the file moved.

Why this is safe to land from a Windows machine

Pruning on one platform and landing on another is only safe if the lint result is
platform-independent. Each of the six was re-linted directly with an empty
suppressions file and no-unused-vars forced to error:

npx eslint --no-ignore --suppressions-location <empty> \
  --rule '{"@typescript-eslint/no-unused-vars":"error"}' <the six files>
→ zero violations

no-unused-vars is platform-independent, so the Windows dev machine and the Linux
CI runner produce the same pruned file. This is the check that makes the change
reviewable, so it is recorded here rather than left as "trust me, it's just
unused vars".

Verification (release/v3.8.52 @ dbe703a)

Check Before After
npm run lint exit 2 exit 0
lint:json totals (11786 files) — 0 errors, 0 warnings
suppressed violations 1094 1088
diff — exactly 6 removals

The ratchet metric does not move. The baseline note for eslintWarnings already
records that lint:json measures 0 with suppressions applied, and a pruned
entry was not counted before or after — so no --update to quality-baseline.json
is warranted (and none was made).

Tests

tests/unit/build/eslint-suppressions-pruned.test.ts — 6/6.

Five are cheap and pin the invariant: the six known-stale entries stay gone, no
empty file/rule buckets survive a hand-edit, and suppression keys stay
POSIX-relative. That last one matters more than it looks — a backslash key
silently stops matching, which turns a suppressed warning back into a hard
failure and, from the gate's exit code, is indistinguishable from "unpruned".

The sixth runs the real ESLint gate on the real repo (~3.5 min) and asserts it
does not exit 2.

The test that guards the tests

There is a SHAPE-SANITY case because the first draft of this suite got the JSON
nesting backwards. The suppressions file is keyed file → rule → count, not
rule → file → count:

{ "src/file.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } } }

With the nesting inverted, every lookup missed and "none of the six known-stale
suppressions is still present" passed vacuously — green, while the gate was
red. The sanity case pins the shape so the other four cannot silently stop
testing anything.

⚠️ base-red inherited: #15246.

Ordering note

This PR unblocks the lint job for every other #15159 PR. #15281 (G-01/G-02)
and #15282 (G-04) will show a red lint step until this lands, because the base
branch carries the unpruned suppressions. Merging this one first is the cheapest
path to a readable CI signal on the rest.

Refs #15159 (G-05)

…xit 2 (diegosouzapw#15159 G-05)

ESLint fails an entire run when the suppressions file carries entries whose
violation no longer occurs:

  There are suppressions left that do not occur anymore. To resolve this,
  re-run the command with `--prune-suppressions` …

So the debt is not cosmetic — `npm run lint` exited **2** on every PR. The step
in ci.yml is the `lint` job, which gates every other job through its own failure.

Six entries were stale. All six are `@typescript-eslint/no-unused-vars`:

  open-sse/translator/request/openai-to-cursor.ts                        (3)
  src/app/(dashboard)/.../settings/components/SystemStorageTab.tsx       (1)
  src/app/api/v1/vscode/[token]/combos/route.ts                         (1)
  src/app/api/v1/vscode/raw/[token]/combos/route.ts                     (1)
  src/lib/oauth/providers/ghe-copilot.ts                                (1)
  tests/unit/cursor-agent-session.test.ts                                (?)

Each was re-linted directly with an EMPTY suppressions file and
`no-unused-vars` forced to error: zero violations on all six. That check is also
why pruning is safe to do on one platform and land on another — `no-unused-vars`
is platform-independent, so the Windows dev machine and the Linux CI runner
produce the same file. 1094 → 1088 suppressed violations; nothing else changed.

## Verification (release/v3.8.52 @ dbe703a)

| Check | Before | After |
| --- | --- | --- |
| `npm run lint` | **exit 2** | **exit 0** |
| `lint:json` totals (11786 files) | — | 0 errors, 0 warnings |
| suppressed violations | 1094 | 1088 |
| diff | — | exactly 6 removals, 1 insertion (trailing comma) |

The ratchet metric does not move: the baseline note already records that
`lint:json` measures 0 with suppressions applied, and a pruned entry was not
counted before or after.

## Tests

`tests/unit/build/eslint-suppressions-pruned.test.ts` — 6/6.

Five are cheap and pin the invariant: the six known-stale entries stay gone, no
empty file/rule buckets survive a hand-edit, suppression keys stay
POSIX-relative (a backslash key silently stops matching, which looks identical
to "unpruned" from the gate's exit code), and a `SHAPE-SANITY` case pins that the
file is keyed by FILE first, then rule.

That last one exists because the first draft of this suite got the nesting
backwards — every lookup missed, and "no stale entries" passed **vacuously** while
the gate was red. A test that cannot fail proves nothing; the sanity case is what
makes the other four trustworthy.

The sixth runs the real ESLint gate on the real repo (~3.5 min) and asserts it
does not exit 2.

Refs diegosouzapw#15159 (G-05)
The CI lint job is the real gate; keep only the cheap invariant checks.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
@diegosouzapw
diegosouzapw merged commit d59c244 into diegosouzapw:release/v3.8.52 Oct 2, 2026
37 of 41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants