fix: enable copilot-local ESLint rules in PR CI#316121
Merged
benvillalobos merged 2 commits intoMay 13, 2026
Merged
Conversation
The copilot eslint plugin used \import * as glob from 'glob'\ and called
\glob.sync()\. In ESM context (which is how the root eslint.config.js loads
this plugin), the namespace import on the CJS glob package produces
\{ default: [function] }\ — no \sync\ property. This causes a silent failure
that prevents ALL copilot-local/* ESLint rules from running in PR CI.
Replace with \s.readdirSync\ which has no external dependency and works
identically for listing .ts files in a single directory.
Impact: Enables copilot-local/no-unlayered-files and 10+ other custom lint
rules in PR CI. These rules have been silently broken since the root eslint
integration was added on April 28 (371c4a0).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes the Copilot local ESLint plugin’s rule discovery so that copilot-local/* rules actually load and run under the repo’s ESM-based ESLint configuration, restoring enforcement in PR CI.
Changes:
- Replace
glob.sync('*.ts', …)withfs.readdirSync(…)to enumerate rule files without relying on CJSglobinterop. - Keep the existing filtering to exclude
index.tsandutils.ts, and dynamically import each rule module.
Show a summary per file
| File | Description |
|---|---|
| extensions/copilot/.eslintplugin/index.ts | Switches rule-file enumeration from glob.sync to fs.readdirSync to avoid ESM/CJS interop issues and ensure copilot-local/* rules load in ESLint. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 0
The rule checked absolute paths for layer folder names. Since 'vscode' is both a valid layer name AND the repo checkout directory, every file's absolute path matched → the rule never fired. Now strips the path to be relative to extensions/copilot/ before checking, so only actual layer folders within the extension structure are matched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
vijayupadya
approved these changes
May 13, 2026
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.
🤖 AI Assisted PR
Problem
The Copilot ESLint plugin (
.eslintplugin/) has two bugs that preventcopilot-local/*rules from running in PR CI:Bug 1 — Plugin fails to load.
index.tsusesimport * as glob from 'glob'+glob.sync(). In the root ESLint's ESM context, the namespace import on CJSglobv5 gives{ default: [function] }— nosyncproperty. The plugin silently fails to load, registering zero rules.Bug 2 — Rule checks absolute paths.
no-unlayered-files.tssplitscontext.filename(an absolute path) and checks if ANY segment matches a layer name. Sincevscodeis both a valid layer name AND the repo checkout directory, every file's path matches → the rule never fires.Impact
Since the root ESLint integration was added on April 28 (
371c4a0a73c), nocopilot-local/*rules have executed in PR CI. This includesno-unlayered-files,no-test-imports,no-runtime-import, and 8+ other custom rules.On May 7, PR #314783 added a test file to the wrong folder. PR CI passed (copilot rules were dead). The ADO build caught it post-merge, causing 6 consecutive build failures across all platforms (~55 cascading task failures, 2-hour recovery window).
Data:
Download Copilot VSIXis the #1 root-cause task in the 14-day OData dataset (81 of 479 root-cause failures). 68% of those were caused by Copilot Lint failures that escaped PR CI.Fix
Two changes in
extensions/copilot/.eslintplugin/:1.
index.ts— Replaceglob.syncwithfs.readdirSyncThe
globpackage (v5, CJS) doesn't exportsyncas an ESM named export.fs.readdirSyncis a Node.js built-in — zero external dependency, works in both CJS and ESM.2.
no-unlayered-files.ts— Use relative pathStrip the absolute path to be relative to
extensions/copilot/before checking for layer folders. This prevents the repo directory name (vscode) from being matched as a valid layer.Verification
Local
Verified locally: bad files caught ✅, correctly layered files pass ✅, no regressions on existing codebase ✅
CI verification PRs (now closed)
PR #316123 + ADO build 438627 prove the current state: CI passes what ADO fails on. This PR closes that gap.
Root cause chain
eslint.config.jsintegrated Copilot ESLint plugin (371c4a0a73c)import * as globdoesn't work in ESM → plugin silently fails to load (Bug 1)no-unlayered-fileschecks absolute paths containingvscodedir → never fires (Bug 2)npm run lintstep to Copilot CI jobs — but both bugs persist, so the step is ineffectiveReferences
371c4a0a73c(April 28)import * as glob from 'glob'→glob.syncisundefined(glob v5 is CJS, namespace import gives{ default }only)