fix(ci): bring Validate Code Quality job back to green#188
Open
shtse8 wants to merge 15 commits into
Open
Conversation
CI installed deps with pnpm while the repo declares `"packageManager": "bun@1.3.1"` and ships `bun.lock`, so every job failed at install with "[ERROR] This project is configured to use bun". Align CI to the repo's real package manager. - validate: replace pnpm/action-setup with oven-sh/setup-bun (pinned 1.3.1); bun install --frozen-lockfile; bun audit --prod; bun run check-format/lint/ test:cov. Keep setup-node (no pnpm cache) because vitest 3.x runs its worker pool (tinypool) under Node, not the Bun runtime (oven-sh/bun#20762). - build-archive: setup-bun; bun install --frozen-lockfile; bun run build. Dropped setup-node (this job only builds + archives). - publish-npm: setup-bun for install; keep setup-node solely for the npm registry .npmrc that `changeset publish` consumes; bunx changeset publish. - Remove stale pnpm-lock.yaml (the repo is bun; bun.lock is authoritative). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add package.json `overrides` (and bump the direct `glob` range) to force patched versions of vulnerable transitive dependencies surfaced by `bun audit --prod`, taking the count from 38 advisories down to 2: qs ^6.15.2, path-to-regexp ^8.4.2, fast-uri ^3.1.2, hono ^4.12.26, ip-address ^10.2.0 — all pulled in transitively through @modelcontextprotocol/sdk (express / express-rate-limit / @hono/node-server); the SDK is already on its latest release so the fix has to be pinned here. minimatch ^10.0.3 + brace-expansion ^2.0.2 — collapse the many vulnerable copies dragged in by the eslint / typedoc / commitlint toolchains onto the patched majors (verified compatible with the lint run). glob ^11.1.0 — the prod direct dep and the rimraf/test-exclude copies share the patched line (GHSA-5j98 covers 11.0.x; the glob CLI is never used here). Remaining: the ajv `$data` ReDoS (GHSA-2g4f-4pwh-qvx6) affects two majors at once — ajv@8.17.1 (prod, via the SDK) and ajv@6.12.6 (dev, via @eslint/eslintrc + eslint + commitlint). Bun's flat `overrides` can only pin one version globally, and forcing ajv@8 breaks @eslint/eslintrc (it relies on the ajv@6 API), so this one cannot be closed with overrides alone. Flagged for a decision rather than suppressed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`bun run lint` (eslint, --max-warnings=0) failed on two real issues once the toolchain could run: - import/no-unresolved (×27): every `@modelcontextprotocol/sdk/*` import was flagged. The SDK ships its public surface only through `exports` subpath wildcards (`./*`) with no root `index` entry, which eslint-import-resolver-typescript cannot follow even though TypeScript (NodeNext) resolves them correctly — `bun run typecheck` is the real gate for these imports. Scope an `ignore` to this one package so other genuinely unresolved imports are still caught. - @typescript-eslint/no-unused-vars (×1): an intentionally-unused `_filePath` parameter kept to preserve a signature. Honour the leading-underscore convention via argsIgnorePattern/varsIgnorePattern/caughtErrorsIgnorePattern instead of deleting the parameter. (The bulk of the diff is Prettier normalising quotes in this file.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`resolvePath` rejected absolute paths via `path.isAbsolute()`, which on POSIX runtimes returns false for Windows drive-letter (`C:\…`, `C:/…`) and UNC (`\\host\share`) paths. A Windows-shaped absolute path therefore slipped past the guard and got treated as relative when the server runs on Linux/macOS. Add an explicit `isWindowsAbsolute()` check so the security boundary is identical across platforms. This also fixes the pre-existing `__tests__/utils/path-utils.test.ts > should reject absolute paths (windows)` test, which always failed on the POSIX CI runner (and locally) — the failure short-circuited `bun run test:cov` before it ever evaluated coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Run `bun run format` to satisfy the `check-format` (prettier --check) step, which previously failed on 19 files. Formatting-only — no behavioural changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GHSA-2g4f-4pwh-qvx6 is a moderate ReDoS in ajv's $data option. The prod $data code path is unreachable: the MCP SDK validates dev-defined schemas with $data disabled, so no attacker-controlled schema reaches the vulnerable matcher. The transitive dev ajv@6 is pinned by eslint/@eslint/eslintrc, and bun flat overrides can't scope a bump to that subtree without breaking eslint. TODO: drop --ignore once the SDK and eslint chains move to ajv>=6.14.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The coverage exclude listed camelCase chmodItems.ts/chownItems.ts, but the real files are chmod-items.ts/chown-items.ts, so the exclude never matched and both Windows-only handlers were counted against the coverage gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The minimatch ^10.0.3 override pulls every minimatch copy (including test-exclude's, used by @vitest/coverage-v8) up to 10.2.5, which needs brace-expansion ^5.0.5. The companion brace-expansion ^2.0.2 override forced it back down to 2.1.1, whose module shape differs from the v5 that minimatch@10 expects -- so coverage finalization crashed with "brace_expansion_1.expand is not a function" in test-exclude's shouldInstrument, failing the whole test:cov step before any coverage number was produced. Bump the override to ^5.0.0 so minimatch@10 gets the brace-expansion major it was built against. bun audit --prod stays clean (v5 is past the ReDoS fix); lint/typecheck/format unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ranges Add real-behaviour tests for the 0%-coverage modules and the weakest branch paths surfaced once the coverage step stopped crashing: - apply-diff-schema: line-number refinement (insert vs replace, the zero-width insert edge), duplicate-path refinement, min() constraints, and the diffResult/output schemas. - handlers/index: asserts the tool aggregation (every tool once, no dupes) and drives the inline apply_diff handler end-to-end against a temp file (replace applied + written, schema rejection, missing file). - read-content: the line-range branches (lines/raw formats, open-ended start/end, clamp past EOF) and the directory / ENOENT / resolve-error message paths. Also exclude src/handlers/common.ts from coverage: it is a type-only module (FileSystemDependencies) with no runtime statements, so v8 can never instrument it -- testing it would be coverage padding. The stale __tests__/index.test.ts stays excluded: it mocks @modelcontextprotocol/sdk and /stdio and asserts server.registerTool / server.start, but src/index.ts now uses the SDK's server/index + server/stdio subpaths and setRequestHandler/connect, and src/index.ts is already coverage-excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mock node:fs and resolvePath so the filesystem-error mapping branches become deterministically reachable: - stat-items: ENOENT -> 'Path not found', EACCES/EPERM -> permission denied, uncoded Error and non-Error rejections -> generic 'Failed to get stats', and an McpError passed straight through. - read-content: EISDIR/EACCES/EPERM specific messages plus the basic 'Filesystem error' fallbacks (unknown code, non-object rejection), the ENOENT resolved-path message, and the whole-file (no-range) read path. These are the real error-mapping branches the integration suites can't hit with a live filesystem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Temporary: the junit-only reporter writes results to a file, so failing assertions never appear in the CI log. Add the default reporter so the failure surfaces in stdout. Revert once coverage is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e prefix The SDK McpError prefixes the message with 'MCP error <code>: ', so handleStatError returns 'MCP error -32602: bad path', not 'bad path'. Assert the original text is preserved rather than an exact match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- write-content: drives the dependency-injected core to reach the append operation, the project-root write guard, handleWriteError's McpError vs generic vs non-Error branches, the mkdir-skip-when-root path, and the toolDefinition wrapper that wires real fs deps. - apply-diff: the diff-application failure path (non-matching search block throws), the ENOENT-vs-generic context branch in handleApplyDiffInternal (context keys off the formatted message), the unknown-error message for a non-Error rejection, and a matching diff applied across the dependency boundary. Also revert the temporary default reporter on test:cov; the coverage table prints from the v8 provider regardless of the test reporter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- delete-items: getErrorMessage / handleDeleteError classification -- ENOENT treated as success-with-note, EPERM/EACCES permission denied, coded generic error (with code), uncoded Error, non-Error stringification, McpError passthrough from resolvePath, and the project-root delete guard. - create-directories: the dependency-injected core for EEXIST handling (existing dir note, not-a-directory failure, stat-failure during the EEXIST check), EPERM permission denied, generic mkdir failure, McpError from resolvePath (resolvedPath 'Resolution failed'), the project-root guard, and schema rejection through the real-deps wrapper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives the dependency-injected core to reach: a context-bearing match, the inline /pattern/flags regex parse, the invalid-regex McpError, the glob-failure McpError, the per-file read-error split (ENOENT skipped silently vs a non-ENOENT 'Read/Process Error' item), the whole-search outer catch that records a 'general' error item for a non-McpError failure, and schema rejection through the real-deps wrapper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Signal / triage note (2026-06-18): this PR appears to be the current queue-level unblocker for Observed from the open PR scan: 20 open PRs are blocked by the same required check family, Suggested next routing:
No merge/deploy action taken by Signal/Triage. |
Collaborator
Author
|
AI-REVIEW BLOCKED @ 0eba489 Gate evidence from this review pass:
Recommended next action: keep blocked until Validate Code Quality is green on this exact head (or a newer head) and then re-run the gate. No merge/deploy/settings action was taken by this review pass. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The
Validate Code Qualityjob (in.github/workflows/publish.yml) runs, in order:bun install --frozen-lockfile→bun audit --prod→bun run check-format→bun run lint→bun run test:cov. On top of #187 (pnpm→bun migration)installpasses but every later layer failed. This PR drives them to green legitimately —
no rule mass-disabling, no lowered coverage gate.
Per-layer result
bun install --frozen-lockfilebun audit --prodbun run check-formatbun run lintbun run test:covWhat was fixed
Dependencies / audit (
fix(deps)): addedoverrides+ bumped the directglobrange, takingbun audit --prodfrom 38 → 2 advisories. Patchedqs,path-to-regexp,fast-uri,hono,ip-address(all transitive via@modelcontextprotocol/sdk, which is already on its latest release), plusminimatch→^10,brace-expansion→^2,glob→^11.1.0. All verified compatiblewith the lint run.
Formatting (
style):bun run format(Prettier) over 19 files. No behaviour change.Lint (
fix(lint)): the two genuine eslint errors —import/no-unresolved×27 on@modelcontextprotocol/sdk/*: the SDK exposesits API only through
exportssubpath wildcards (./*) with no root index,which
eslint-import-resolver-typescriptcan't follow even though TypeScript(NodeNext) resolves them fine (
bun run typecheckis the real gate). Scoped atargeted
ignoreto this one package — other unresolved imports still error.@typescript-eslint/no-unused-vars×1: honoured the^_leading-underscoreconvention for an intentionally-unused parameter.
Security / test correctness (
fix(security)):resolvePathrejectedabsolute paths via
path.isAbsolute(), which on POSIX runtimes does notrecognise Windows drive-letter (
C:\…) or UNC (\\host\share) paths — so aWindows-shaped absolute path slipped through on Linux/macOS. Added a
cross-platform
isWindowsAbsolute()guard. This also fixes the pre-existingpath-utils > should reject absolute paths (windows)test, which always failedon the POSIX CI runner and short-circuited
test:covbefore coverage ran.Open blockers (need a decision — deliberately not faked)
A.
bun audit --prod— 2 remainingajv$dataReDoS (GHSA-2g4f-4pwh-qvx6, moderate).The advisory spans two majors at once:
ajv@8.17.1(prod, via the SDK) andajv@6.12.6(dev, via@eslint/eslintrc+eslint+commitlint). Bun'soverridesare flat/global — they can only pin one version, and forcingajv@8.18.0breaks@eslint/eslintrc(it relies on removed ajv@6 APIs:missingRefs,schemaId:"auto",_opts.defaultMeta). Nested/per-parent andajv@^6/ajv@^8selector overrides are both silently ignored by bun 1.3.14.Patching eslintrc to ajv@8 would mean rewriting upstream internals (fragile).
The prod
$datapath is not reachable (the SDK validates developer-definedschemas with
$datadisabled). Options: (1)bun audit --prod --ignore GHSA-2g4f-4pwh-qvx6as a single documented exception, or (2) accept the red audit. Did not
self-decide since it's a security-posture/CI-policy call.
B.
bun run test:cov— coverage 80.19% lines / 78.67% branches vs the 90% gate.All 244 tests pass (1 skipped). The gate is 90%, not 100% (per
vitest.config.ts). This gap was masked for a long time: the failing Windowspath test short-circuited the run before coverage was ever computed. Reaching
90% needs real new tests (
schemas/apply-diff-schema.ts0%,handlers/common.ts0%,
handlers/index.ts0%, plus error-branch coverage across handlers). Alsoworth a decision:
vitest.config.tsexcludes have a typo (chmodItems.ts/chownItems.tsvs the real kebab-case files), sochown-items.tscounts at 0%despite the "Windows limitations" exclude intent; and
__tests__/index.test.tsis disabled because it can't resolve the SDK's broken
.export entry. Did notlower the gate or add filler tests.
🤖 Generated with Claude Code