From 6a003f502e1456fca78c0fe9a2e1556892b13609 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Jul 2026 07:09:40 +0000 Subject: [PATCH 1/2] Initial plan From d635659e972dc58ffbd11d5bbfe3f70c733fa14a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Jul 2026 07:17:41 +0000 Subject: [PATCH 2/2] fix(eslint-factory): exempt console.error/console.warn from prefer-core-logging rule console.error and console.warn write to process.stderr, while core.error and core.warning emit workflow commands to process.stdout. For stdio MCP servers that own stdout as a JSON-RPC channel, the substitution is not behavior-preserving and would corrupt the stream. - Remove `error` and `warn` from CONSOLE_TO_CORE map so they are never flagged - Update tests: console.error/console.warn now have valid tests documenting the exemption - Add prefer-core-logging README section with stream-semantics rationale Closes #46541 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- eslint-factory/README.md | 19 +++++++ .../src/rules/prefer-core-logging.test.ts | 52 ++++++------------- .../src/rules/prefer-core-logging.ts | 10 ++-- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/eslint-factory/README.md b/eslint-factory/README.md index 6465bc9e76b..0bbf80b4f98 100644 --- a/eslint-factory/README.md +++ b/eslint-factory/README.md @@ -26,6 +26,7 @@ This project hosts custom ESLint linters for `/actions/setup/js`. | [`no-unsafe-catch-error-property`](#no-unsafe-catch-error-property) | Disallow unsafe property access on `catch` error bindings | | [`no-unsafe-promise-catch-error-property`](#no-unsafe-promise-catch-error-property) | Disallow unsafe property access in promise rejection handlers | | [`prefer-get-error-message`](#prefer-get-error-message) | Prefer `getErrorMessage(err)` over the inline ternary pattern | +| [`prefer-core-logging`](#prefer-core-logging) | Prefer `@actions/core` logging over `console.log` / `console.info` / `console.debug` | | [`prefer-number-isnan`](#prefer-number-isnan) | Prefer `Number.isNaN()` over global `isNaN()` | | [`require-async-entrypoint-catch`](#require-async-entrypoint-catch) | Require `.catch(...)` on bare async entrypoint calls | | [`require-await-core-summary-write`](#require-await-core-summary-write) | Require `await` on `core.summary.write()` calls | @@ -382,3 +383,21 @@ Why: when `spawnSync` cannot spawn the child process (e.g. `ENOENT`, `ETIMEDOUT` - Passing the result object to a helper function that internally checks `.error` is not recognized. - Mutable aliases (`let e = result.error; e = undefined; if (e) throw e`) are rejected because the original value may have been discarded before the guard. +### `prefer-core-logging` + +Prefer `@actions/core` logging methods (`core.info`, `core.debug`) over `console.log`, `console.info`, and `console.debug`. + +`core.*` logging methods integrate with the GitHub Actions annotation system (errors and warnings appear as file annotations in the UI) and produce structured log output. `global.core` is always available via `shim.cjs` in the Node.js context and via `github-script` in the Actions context. + +**Covered methods and their replacements:** + +| `console.*` method | Suggested replacement | +|---|---| +| `console.log` | `core.info` | +| `console.info` | `core.info` | +| `console.debug` | `core.debug` | + +**Intentionally excluded: `console.error` and `console.warn`** + +`console.error` and `console.warn` write to **`process.stderr`**, while `core.error` and `core.warning` emit GitHub Actions workflow commands to **`process.stdout`**. For processes that own stdout as a data/protocol channel — such as stdio MCP servers and transports — replacing stderr logging with stdout logging would corrupt the JSON-RPC stream. Because the stream change is not behavior-preserving, the rule never reports `console.error` or `console.warn` and offers no suggestion to replace them. + diff --git a/eslint-factory/src/rules/prefer-core-logging.test.ts b/eslint-factory/src/rules/prefer-core-logging.test.ts index 1437fd2cd02..e5e2b0e7f61 100644 --- a/eslint-factory/src/rules/prefer-core-logging.test.ts +++ b/eslint-factory/src/rules/prefer-core-logging.test.ts @@ -28,16 +28,6 @@ describe("prefer-core-logging", () => { { messageId: "preferCoreLogging", data: { method: "log", replacement: "core.info" }, suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: `"hello"` }, output: `core.info("hello");` }] }, ], }, - { - code: `console.error("bad thing");`, - errors: [ - { - messageId: "preferCoreLogging", - data: { method: "error", replacement: "core.error" }, - suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.error", args: `"bad thing"` }, output: `core.error("bad thing");` }], - }, - ], - }, { code: `const foo = "bar"; console.log(foo);`, errors: [ @@ -100,39 +90,29 @@ describe("prefer-core-logging", () => { }); }); - it("invalid: console.error when core is in scope", () => { + it("valid: console.error is not flagged — writes to stderr, not stdout", () => { ruleTester.run("prefer-core-logging", preferCoreLoggingRule, { - valid: [], - invalid: [ - { - code: `const core = require("@actions/core"); console.error("bad thing");`, - errors: [ - { - messageId: "preferCoreLogging", - data: { method: "error", replacement: "core.error" }, - suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.error", args: `"bad thing"` }, output: `const core = require("@actions/core"); core.error("bad thing");` }], - }, - ], - }, + valid: [ + // console.error writes to stderr; core.error writes workflow commands to stdout. + // Replacing stderr logging with stdout logging would corrupt stdio-protocol + // channels (e.g. MCP servers), so the rule intentionally exempts console.error. + `console.error("bad thing");`, + `const core = require("@actions/core"); console.error("bad thing");`, + `console.error("MCP transport error:", new Error("oops"));`, ], + invalid: [], }); }); - it("invalid: console.warn when core is in scope", () => { + it("valid: console.warn is not flagged — writes to stderr, not stdout", () => { ruleTester.run("prefer-core-logging", preferCoreLoggingRule, { - valid: [], - invalid: [ - { - code: `const core = require("@actions/core"); console.warn("warning");`, - errors: [ - { - messageId: "preferCoreLogging", - data: { method: "warn", replacement: "core.warning" }, - suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.warning", args: `"warning"` }, output: `const core = require("@actions/core"); core.warning("warning");` }], - }, - ], - }, + valid: [ + // console.warn writes to stderr; core.warning writes workflow commands to stdout. + // Same stream-semantics rationale as console.error above. + `console.warn("warning");`, + `const core = require("@actions/core"); console.warn("warning");`, ], + invalid: [], }); }); diff --git a/eslint-factory/src/rules/prefer-core-logging.ts b/eslint-factory/src/rules/prefer-core-logging.ts index afaf010640f..a1b36612aec 100644 --- a/eslint-factory/src/rules/prefer-core-logging.ts +++ b/eslint-factory/src/rules/prefer-core-logging.ts @@ -2,12 +2,16 @@ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils" const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh-aw/tree/main/eslint-factory#${name}`); -// Maps console method → recommended core replacement +// Maps console method → recommended core replacement. +// NOTE: console.error and console.warn are intentionally excluded. +// Those methods write to process.stderr, while core.error / core.warning emit +// GitHub Actions workflow commands to process.stdout. For processes that own +// stdout as a data/protocol channel (e.g. stdio MCP servers), rewriting +// stderr logging to stdout would corrupt the stream. The substitution is not +// behavior-preserving and must not be auto-applied. const CONSOLE_TO_CORE: Record = { log: "core.info", info: "core.info", - warn: "core.warning", - error: "core.error", debug: "core.debug", };