From eb678dc8266350a2b21742e1cb6569e1f451f31d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:33:37 +0000 Subject: [PATCH 1/5] Initial plan From ee05c3c935d5bd1694ab9f735b53f7180c6eb5c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:45:00 +0000 Subject: [PATCH 2/5] fix(setup-js): use core.info in OTLP and outcome scripts Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/action_conclusion_otlp.cjs | 7 ++-- actions/setup/js/action_setup_otlp.cjs | 17 +++++----- actions/setup/js/ai_credits_context.cjs | 11 ++++--- actions/setup/js/emit_outcome_spans.cjs | 9 ++--- actions/setup/js/evaluate_outcomes.cjs | 13 ++++---- .../setup/js/generate_safe_outputs_tools.cjs | 7 ++-- actions/setup/js/test-live-github-api.cjs | 33 ++++++++++--------- 7 files changed, 50 insertions(+), 47 deletions(-) diff --git a/actions/setup/js/action_conclusion_otlp.cjs b/actions/setup/js/action_conclusion_otlp.cjs index 55e2e1cb2af..080581b3cab 100644 --- a/actions/setup/js/action_conclusion_otlp.cjs +++ b/actions/setup/js/action_conclusion_otlp.cjs @@ -1,5 +1,6 @@ // @ts-check "use strict"; +require("./shim.cjs"); /** * action_conclusion_otlp.cjs @@ -77,15 +78,15 @@ async function run() { const startMs = parseJobStartMs(process.env.GITHUB_AW_OTEL_JOB_START_MS); if (endpoints) { - console.log(`[otlp] sending conclusion span "${spanName}" to configured endpoints`); + core.info(`[otlp] sending conclusion span "${spanName}" to configured endpoints`); } else { - console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)"); + core.info("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)"); } await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs }); if (endpoints) { - console.log("[otlp] conclusion span export attempted"); + core.info("[otlp] conclusion span export attempted"); } } diff --git a/actions/setup/js/action_setup_otlp.cjs b/actions/setup/js/action_setup_otlp.cjs index bd4d764159b..374987b25ad 100644 --- a/actions/setup/js/action_setup_otlp.cjs +++ b/actions/setup/js/action_setup_otlp.cjs @@ -1,5 +1,6 @@ // @ts-check "use strict"; +require("./shim.cjs"); /** * action_setup_otlp.cjs @@ -42,7 +43,7 @@ function writeEnvLine(filePath, key, value, logLabel, fileLabel) { if (!filePath || !value) return; try { appendFileSync(filePath, `${key}=${value}\n`); - console.log(`[otlp] ${logLabel} written to ${fileLabel}`); + core.info(`[otlp] ${logLabel} written to ${fileLabel}`); } catch { /* ignore */ } @@ -112,13 +113,13 @@ async function run() { // through GitHub Actions expression evaluation is unreliable. const inputTraceId = getActionInput("TRACE_ID").toLowerCase(); if (inputTraceId) { - console.log(`[otlp] INPUT_TRACE_ID=${inputTraceId} (will reuse activation trace)`); + core.info(`[otlp] INPUT_TRACE_ID=${inputTraceId} (will reuse activation trace)`); } else { - console.log("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated"); + core.info("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated"); } const inputParentSpanId = getActionInput("PARENT_SPAN_ID").toLowerCase(); if (inputParentSpanId) { - console.log(`[otlp] INPUT_PARENT_SPAN_ID=${inputParentSpanId} (will parent setup span)`); + core.info(`[otlp] INPUT_PARENT_SPAN_ID=${inputParentSpanId} (will parent setup span)`); } // Normalize to the canonical underscore form so sendJobSetupSpan (which @@ -148,9 +149,9 @@ async function run() { } if (!endpoints) { - console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span"); + core.info("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span"); } else { - console.log(`[otlp] sending setup span to configured endpoints`); + core.info(`[otlp] sending setup span to configured endpoints`); } const { traceId, spanId, parentSpanId } = await sendJobSetupSpan({ @@ -159,10 +160,10 @@ async function run() { parentSpanId: inputParentSpanId || undefined, }); - console.log(`[otlp] resolved trace-id=${traceId}`); + core.info(`[otlp] resolved trace-id=${traceId}`); if (endpoints) { - console.log(`[otlp] setup span sent (traceId=${traceId}, spanId=${spanId})`); + core.info(`[otlp] setup span sent (traceId=${traceId}, spanId=${spanId})`); } const githubOutput = process.env.GITHUB_OUTPUT; diff --git a/actions/setup/js/ai_credits_context.cjs b/actions/setup/js/ai_credits_context.cjs index 01ecef07888..bd5611f543e 100644 --- a/actions/setup/js/ai_credits_context.cjs +++ b/actions/setup/js/ai_credits_context.cjs @@ -1,4 +1,5 @@ // @ts-check +require("./shim.cjs"); const fs = require("fs"); const path = require("path"); @@ -323,10 +324,10 @@ function parseAuditLogCombined(auditJsonlPathOverride) { * @param {string} envVarName */ function logAICreditSource(label, auditValue, stdioValue, envValue, envVarName) { - if (auditValue) console.log(`[ai-credits] ${label} source=audit_log value=${auditValue}`); - else if (stdioValue) console.log(`[ai-credits] ${label} source=agent_stdio value=${stdioValue}`); - else if (envValue) console.log(`[ai-credits] ${label} source=env(${envVarName}) value=${envValue}`); - else console.log(`[ai-credits] ${label} source=none ${envVarName}=${process.env[envVarName] || "(unset)"}`); + if (auditValue) core.info(`[ai-credits] ${label} source=audit_log value=${auditValue}`); + else if (stdioValue) core.info(`[ai-credits] ${label} source=agent_stdio value=${stdioValue}`); + else if (envValue) core.info(`[ai-credits] ${label} source=env(${envVarName}) value=${envValue}`); + else core.info(`[ai-credits] ${label} source=none ${envVarName}=${process.env[envVarName] || "(unset)"}`); } /** @@ -355,7 +356,7 @@ function resolveAICreditsFailureState({ logProvenance = true } = {}) { : envRateLimitSignal ? "env_ignored_no_ai_credits" : "none"; - console.log(`[ai-credits] rateLimitSignal source=${rawRateLimitSignalSource}`); + core.info(`[ai-credits] rateLimitSignal source=${rawRateLimitSignalSource}`); } const aiCredits = auditAICredits || stdioSignals.aiCredits || envAICredits || ""; diff --git a/actions/setup/js/emit_outcome_spans.cjs b/actions/setup/js/emit_outcome_spans.cjs index 01d73655ca9..49d6fd0b74e 100644 --- a/actions/setup/js/emit_outcome_spans.cjs +++ b/actions/setup/js/emit_outcome_spans.cjs @@ -1,5 +1,6 @@ // @ts-check /// +require("./shim.cjs"); /** * emit_outcome_spans.cjs @@ -76,7 +77,7 @@ async function main() { const awInfo = readJSONIfExists(AW_INFO_PATH) || {}; if (evaluations.length === 0 && (!summary || summary.total_outcomes === 0)) { - console.log("[outcome-otel] No outcome evaluations to export"); + core.info("[outcome-otel] No outcome evaluations to export"); return; } @@ -84,7 +85,7 @@ async function main() { const hasEndpoints = endpoints.length > 0; if (!hasEndpoints) { - console.log("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only"); + core.info("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only"); } // Read aw_info.json first: GH_AW_INFO_* env vars are only present during setup, @@ -304,11 +305,11 @@ async function main() { // Always write to local JSONL mirror appendToOTLPJSONL(payload); - console.log(`[outcome-otel] Emitting ${evaluations.length} outcome span(s) + 1 summary span`); + core.info(`[outcome-otel] Emitting ${evaluations.length} outcome span(s) + 1 summary span`); if (hasEndpoints) { await sendOTLPToAllEndpoints(endpoints, payload, { skipJSONL: true }); - console.log(`[outcome-otel] Exported to ${endpoints.length} endpoint(s)`); + core.info(`[outcome-otel] Exported to ${endpoints.length} endpoint(s)`); } } diff --git a/actions/setup/js/evaluate_outcomes.cjs b/actions/setup/js/evaluate_outcomes.cjs index 2211644e97c..50c60daaa91 100644 --- a/actions/setup/js/evaluate_outcomes.cjs +++ b/actions/setup/js/evaluate_outcomes.cjs @@ -1,4 +1,5 @@ // @ts-check +require("./shim.cjs"); /** * evaluate_outcomes.cjs @@ -2057,7 +2058,7 @@ function main() { const runsRaw = gh(["run", "list", "--repo", repo, "--limit", "200", "--json", "databaseId,conclusion,workflowName,event", "--jq", '[.[] | select(.conclusion == "success")] | .[0:150]']); if (!runsRaw || runsRaw === "[]" || runsRaw === "null") { - console.log("No recent successful runs found"); + core.info("No recent successful runs found"); writeJSONAtomic(SUMMARY_PATH, { runs_checked: 0, total_outcomes: 0 }); process.exit(0); } @@ -2127,7 +2128,7 @@ function main() { noop += runNoops; - console.log(`Run ${runId} (${workflow}): ${runItems} item(s), ${runNoops} noop(s) [trigger: ${event}]`); + core.info(`Run ${runId} (${workflow}): ${runItems} item(s), ${runNoops} noop(s) [trigger: ${event}]`); checked++; total += runItems; @@ -2297,10 +2298,10 @@ function main() { const merged = [...new Set([...seenIds, ...evaluatedIds])].sort((a, b) => a - b).slice(-500); writeJSONAtomic(SEEN_FILE, merged); - console.log(`āœ“ Checked ${checked} runs, ${total} outcomes`); - console.log(` Accepted: ${accepted}, Rejected: ${rejected}, Ignored: ${ignored}, Pending: ${pending}, Noop: ${noop}`); - console.log(` Acceptance rate: ${acceptanceRate.toFixed(4)}`); - console.log(JSON.stringify(readJSON(SUMMARY_PATH, {}), null, 2)); + core.info(`āœ“ Checked ${checked} runs, ${total} outcomes`); + core.info(` Accepted: ${accepted}, Rejected: ${rejected}, Ignored: ${ignored}, Pending: ${pending}, Noop: ${noop}`); + core.info(` Acceptance rate: ${acceptanceRate.toFixed(4)}`); + core.info(JSON.stringify(readJSON(SUMMARY_PATH, {}), null, 2)); } if (require.main === module) { diff --git a/actions/setup/js/generate_safe_outputs_tools.cjs b/actions/setup/js/generate_safe_outputs_tools.cjs index 7822e3062ea..8d91f5a3324 100644 --- a/actions/setup/js/generate_safe_outputs_tools.cjs +++ b/actions/setup/js/generate_safe_outputs_tools.cjs @@ -1,6 +1,7 @@ // @ts-check /// "use strict"; +require("./shim.cjs"); // @safe-outputs-exempt SEC-004 — schema generator; does not process user body content. The substring "body:" appears only in the comment referencing the "allow-body" config option. /** @@ -401,11 +402,7 @@ async function main() { const debugEnabled = process.env.DEBUG === "*" || (process.env.DEBUG || "").includes("safe_outputs"); if (debugEnabled) { const infoMsg = `Generated tools.json with ${allFilteredTools.length} tools (${filteredTools.length} static + ${dynamicTools.length} dynamic)`; - if (typeof core !== "undefined") { - core.info(infoMsg); - } else { - console.log(infoMsg); - } + core.info(infoMsg); } } diff --git a/actions/setup/js/test-live-github-api.cjs b/actions/setup/js/test-live-github-api.cjs index 912d6861fca..b315b75ca62 100755 --- a/actions/setup/js/test-live-github-api.cjs +++ b/actions/setup/js/test-live-github-api.cjs @@ -1,5 +1,6 @@ #!/usr/bin/env node // @ts-check +require("./shim.cjs"); /** * Standalone script to test frontmatter hash computation with live GitHub API @@ -29,7 +30,7 @@ async function testLiveGitHubAPI() { process.exit(1); } - console.log("šŸ” Testing frontmatter hash with live GitHub API\n"); + core.info("šŸ” Testing frontmatter hash with live GitHub API\n"); // Configuration const owner = "github"; @@ -37,38 +38,38 @@ async function testLiveGitHubAPI() { const ref = "main"; const workflowPath = ".github/workflows/audit-workflows.md"; - console.log(`Repository: ${owner}/${repo}`); - console.log(`Branch: ${ref}`); - console.log(`Workflow: ${workflowPath}\n`); + core.info(`Repository: ${owner}/${repo}`); + core.info(`Branch: ${ref}`); + core.info(`Workflow: ${workflowPath}\n`); try { // Use dynamic import for ESM module compatibility const { getOctokit } = await import("@actions/github"); // Create GitHub API client - console.log("šŸ“” Connecting to GitHub API..."); + core.info("šŸ“” Connecting to GitHub API..."); const octokit = getOctokit(token); // Create file reader using real GitHub API const fileReader = createGitHubFileReader(octokit, owner, repo, ref); // Fetch and compute hash - console.log(`šŸ“„ Fetching workflow from GitHub API...`); + core.info(`šŸ“„ Fetching workflow from GitHub API...`); const hash = await computeFrontmatterHash(workflowPath, { fileReader, }); - console.log(`\nāœ… Success! Hash computed from live GitHub API data:`); - console.log(` ${hash}`); + core.info(`\nāœ… Success! Hash computed from live GitHub API data:`); + core.info(` ${hash}`); // Verify determinism - console.log(`\nšŸ”„ Verifying determinism (fetching again)...`); + core.info(`\nšŸ”„ Verifying determinism (fetching again)...`); const hash2 = await computeFrontmatterHash(workflowPath, { fileReader, }); if (hash === hash2) { - console.log(`āœ… Hashes match - computation is deterministic`); + core.info(`āœ… Hashes match - computation is deterministic`); } else { console.error(`āŒ Error: Hashes don't match!`); console.error(` First: ${hash}`); @@ -77,12 +78,12 @@ async function testLiveGitHubAPI() { } // Summary - console.log(`\nšŸ“Š Summary:`); - console.log(` - Successfully fetched workflow from live GitHub API`); - console.log(` - Processed workflow with imports (shared/mcp/tavily.md, etc.)`); - console.log(` - Computed deterministic SHA-256 hash`); - console.log(` - Verified hash consistency across multiple API calls`); - console.log(`\n✨ All tests passed! The JavaScript implementation works correctly with GitHub API.`); + core.info(`\nšŸ“Š Summary:`); + core.info(` - Successfully fetched workflow from live GitHub API`); + core.info(` - Processed workflow with imports (shared/mcp/tavily.md, etc.)`); + core.info(` - Computed deterministic SHA-256 hash`); + core.info(` - Verified hash consistency across multiple API calls`); + core.info(`\n✨ All tests passed! The JavaScript implementation works correctly with GitHub API.`); } catch (err) { const error = err; console.error(`\nāŒ Error: ${getErrorMessage(error)}`); From 0a9d033cdc0d6ce75bfe7dd240a8bdf5e9e31cba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:25:04 +0000 Subject: [PATCH 3/5] =?UTF-8?q?chore:=20initial=20plan=20=E2=80=94=20updat?= =?UTF-8?q?e=20test=20spies=20from=20console.log=20to=20core.info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index acec3f146cd..615a51e551e 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -15,6 +15,7 @@ Repository overlay (optional): Read only the files you need: Load these files from `github/gh-aw` (they are not available locally). +- `.github/aw/action-container-substitutions.md` - `.github/aw/agentic-chat.md` - `.github/aw/agentic-workflows-mcp.md` - `.github/aw/asciicharts.md` From 14e097919459cd77dec9b3f8f9665b9fe1f04b13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:32:34 +0000 Subject: [PATCH 4/5] fix(setup-js tests): update test spies from console.log to core.info Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/action_conclusion_otlp.test.cjs | 22 ++++++++--- actions/setup/js/action_setup_otlp.test.cjs | 38 ++++++++++++------- actions/setup/js/emit_outcome_spans.test.cjs | 19 ++++++++-- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/actions/setup/js/action_conclusion_otlp.test.cjs b/actions/setup/js/action_conclusion_otlp.test.cjs index 41851bfd307..980433a8f87 100644 --- a/actions/setup/js/action_conclusion_otlp.test.cjs +++ b/actions/setup/js/action_conclusion_otlp.test.cjs @@ -5,6 +5,17 @@ import { createRequire } from "module"; // Use CJS require so we share the same module cache as action_conclusion_otlp.cjs const req = createRequire(import.meta.url); +// Set up global.core mock before loading the module under test so shim.cjs +// sees it already present and does not overwrite it with its stderr-based shim. +const mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), +}; +global.core = mockCore; + // Load the real send_otlp_span module and capture the original function const sendOtlpModule = req("./send_otlp_span.cjs"); const originalSendJobConclusionSpan = sendOtlpModule.sendJobConclusionSpan; @@ -24,7 +35,6 @@ describe("action_conclusion_otlp.cjs", () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(console, "log").mockImplementation(() => {}); mockSendJobConclusionSpan.mockResolvedValue(undefined); // Patch the shared CJS exports object — run() accesses this at call time sendOtlpModule.sendJobConclusionSpan = mockSendJobConclusionSpan; @@ -53,7 +63,7 @@ describe("action_conclusion_otlp.cjs", () => { it("should log that OTLP export is skipped and JSONL mirror will be attempted", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)"); }); it("should still call sendJobConclusionSpan for JSONL mirror", async () => { @@ -78,13 +88,13 @@ describe("action_conclusion_otlp.cjs", () => { it("should log the conclusion span export as attempted", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] conclusion span export attempted"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] conclusion span export attempted"); }); it("should log the endpoint URL in the sending message", async () => { await run(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("configured endpoints")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("configured endpoints")); }); describe("span name construction", () => { @@ -116,7 +126,7 @@ describe("action_conclusion_otlp.cjs", () => { await run(); - expect(console.log).toHaveBeenCalledWith('[otlp] sending conclusion span "gh-aw.setup.conclusion" to configured endpoints'); + expect(mockCore.info).toHaveBeenCalledWith('[otlp] sending conclusion span "gh-aw.setup.conclusion" to configured endpoints'); }); it("should handle different job names correctly", async () => { @@ -198,7 +208,7 @@ describe("action_conclusion_otlp.cjs", () => { mockSendJobConclusionSpan.mockRejectedValueOnce(new Error("fail")); await expect(run()).rejects.toThrow("fail"); - expect(console.log).not.toHaveBeenCalledWith("[otlp] conclusion span export attempted"); + expect(mockCore.info).not.toHaveBeenCalledWith("[otlp] conclusion span export attempted"); }); }); }); diff --git a/actions/setup/js/action_setup_otlp.test.cjs b/actions/setup/js/action_setup_otlp.test.cjs index aaaa50a4ffd..55c995d36bc 100644 --- a/actions/setup/js/action_setup_otlp.test.cjs +++ b/actions/setup/js/action_setup_otlp.test.cjs @@ -7,6 +7,17 @@ import { writeFileSync, readFileSync, mkdtempSync, rmSync } from "fs"; const req = createRequire(import.meta.url); +// Set up global.core mock before loading the module under test so shim.cjs +// sees it already present and does not overwrite it with its stderr-based shim. +const mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), +}; +global.core = mockCore; + // Load send_otlp_span.cjs first so we can patch its exports before run() calls require() // inside action_setup_otlp.cjs. Both share the same CJS module cache, so patching the // exports object here is reflected when run() destructures from require(...send_otlp_span.cjs). @@ -39,7 +50,6 @@ describe("action_setup_otlp.cjs", () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(console, "log").mockImplementation(() => {}); // Patch the shared CJS module exports — run() re-destructures on every call sendOtlpModule.sendJobSetupSpan = mockSendJobSetupSpan; @@ -105,7 +115,7 @@ describe("action_setup_otlp.cjs", () => { it("should log that OTLP export is being skipped", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span"); }); it("should still call sendJobSetupSpan for JSONL mirror", async () => { @@ -117,7 +127,7 @@ describe("action_setup_otlp.cjs", () => { it("should not log 'sending setup span to' when endpoint is absent", async () => { await run(); - const logged = vi.mocked(console.log).mock.calls.flat(); + const logged = vi.mocked(mockCore.info).mock.calls.flat(); expect(logged.some(msg => typeof msg === "string" && msg.includes("sending setup span to"))).toBe(false); }); }); @@ -130,7 +140,7 @@ describe("action_setup_otlp.cjs", () => { it("should log sending the setup span to the configured endpoint", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] sending setup span to configured endpoints"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] sending setup span to configured endpoints"); }); it("should call sendJobSetupSpan exactly once", async () => { @@ -142,14 +152,14 @@ describe("action_setup_otlp.cjs", () => { it("should log setup span sent with traceId and spanId", async () => { await run(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`traceId=${VALID_TRACE_ID}`)); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`spanId=${VALID_SPAN_ID}`)); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`traceId=${VALID_TRACE_ID}`)); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`spanId=${VALID_SPAN_ID}`)); }); it("should log the resolved trace ID", async () => { await run(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`trace-id=${VALID_TRACE_ID}`)); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`trace-id=${VALID_TRACE_ID}`)); }); }); @@ -271,13 +281,13 @@ describe("action_setup_otlp.cjs", () => { await run(); - expect(console.log).toHaveBeenCalledWith(expect.stringContaining("INPUT_TRACE_ID=abcdef0123456789abcdef0123456789")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("INPUT_TRACE_ID=abcdef0123456789abcdef0123456789")); }); it("should log that INPUT_TRACE_ID is not set when absent", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated"); }); it("should accept the hyphen form INPUT_TRACE-ID as a fallback", async () => { @@ -326,7 +336,7 @@ describe("action_setup_otlp.cjs", () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] INPUT_PARENT_SPAN_ID=aabbccddeeff0011 (will parent setup span)"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] INPUT_PARENT_SPAN_ID=aabbccddeeff0011 (will parent setup span)"); }); it("should accept the hyphen form INPUT_PARENT-SPAN-ID as a fallback", async () => { @@ -377,7 +387,7 @@ describe("action_setup_otlp.cjs", () => { it("should log that trace-id was written to GITHUB_OUTPUT", async () => { await run(); - expect(console.log).toHaveBeenCalledWith(`[otlp] trace-id=${VALID_TRACE_ID} written to GITHUB_OUTPUT`); + expect(mockCore.info).toHaveBeenCalledWith(`[otlp] trace-id=${VALID_TRACE_ID} written to GITHUB_OUTPUT`); }); it("should write parent-span-id to GITHUB_OUTPUT when parent span ID is valid", async () => { @@ -466,19 +476,19 @@ describe("action_setup_otlp.cjs", () => { it("should log the GITHUB_AW_OTEL_TRACE_ID write", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_TRACE_ID written to GITHUB_ENV"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_TRACE_ID written to GITHUB_ENV"); }); it("should log the GITHUB_AW_OTEL_PARENT_SPAN_ID write", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_PARENT_SPAN_ID written to GITHUB_ENV"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_PARENT_SPAN_ID written to GITHUB_ENV"); }); it("should log the GITHUB_AW_OTEL_JOB_START_MS write", async () => { await run(); - expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV"); + expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV"); }); }); diff --git a/actions/setup/js/emit_outcome_spans.test.cjs b/actions/setup/js/emit_outcome_spans.test.cjs index cbc8957d4b4..29f0fb0ac0c 100644 --- a/actions/setup/js/emit_outcome_spans.test.cjs +++ b/actions/setup/js/emit_outcome_spans.test.cjs @@ -3,6 +3,18 @@ import fs from "fs"; import { createRequire } from "module"; const req = createRequire(import.meta.url); + +// Set up global.core mock before loading any module that requires shim.cjs so +// the shim sees it already present and does not overwrite it. +const mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), +}; +global.core = mockCore; + const sendOtlpModule = req("./send_otlp_span.cjs"); const mockGenerateTraceId = vi.fn(); @@ -48,7 +60,6 @@ describe("emit_outcome_spans.cjs", () => { beforeEach(() => { vi.clearAllMocks(); - vi.spyOn(console, "log").mockImplementation(() => {}); vi.spyOn(console, "warn").mockImplementation(() => {}); fs.mkdirSync("/tmp/gh-aw", { recursive: true }); @@ -168,7 +179,7 @@ describe("emit_outcome_spans.cjs", () => { it("no-ops when there are no evaluations and no summary data", async () => { await moduleUnderTest.main(); - expect(console.log).toHaveBeenCalledWith("[outcome-otel] No outcome evaluations to export"); + expect(mockCore.info).toHaveBeenCalledWith("[outcome-otel] No outcome evaluations to export"); expect(mockBuildGitHubActionsResourceAttributes).not.toHaveBeenCalled(); expect(mockBuildOTLPBatchPayload).not.toHaveBeenCalled(); expect(mockAppendToOTLPJSONL).not.toHaveBeenCalled(); @@ -323,7 +334,7 @@ describe("emit_outcome_spans.cjs", () => { expect(mockAppendToOTLPJSONL).toHaveBeenCalledOnce(); expect(mockSendOTLPToAllEndpoints).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only"); + expect(mockCore.info).toHaveBeenCalledWith("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only"); }); it("falls back to GH_AW_INFO_* env vars and exports to configured endpoints", async () => { @@ -356,6 +367,6 @@ describe("emit_outcome_spans.cjs", () => { ); expect(mockAppendToOTLPJSONL).toHaveBeenCalledOnce(); expect(mockSendOTLPToAllEndpoints).toHaveBeenCalledWith([{ url: "https://otel.example.com" }], expect.any(Object), { skipJSONL: true }); - expect(console.log).toHaveBeenCalledWith("[outcome-otel] Exported to 1 endpoint(s)"); + expect(mockCore.info).toHaveBeenCalledWith("[outcome-otel] Exported to 1 endpoint(s)"); }); }); From 71698f311a22a8772569ea817ef274413afe7255 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:56:04 +0000 Subject: [PATCH 5/5] fix(setup-js): complete console.error migration in test-live-github-api.cjs Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/test-live-github-api.cjs | 29 ++++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/actions/setup/js/test-live-github-api.cjs b/actions/setup/js/test-live-github-api.cjs index b315b75ca62..853acf34714 100755 --- a/actions/setup/js/test-live-github-api.cjs +++ b/actions/setup/js/test-live-github-api.cjs @@ -20,13 +20,15 @@ async function testLiveGitHubAPI() { // Check for GitHub token const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN; if (!token) { - console.error("āŒ Error: No GitHub token found"); - console.error("Please set GITHUB_TOKEN or GH_TOKEN environment variable"); - console.error("\nExample:"); - console.error(" GITHUB_TOKEN=ghp_xxx node test-live-github-api.cjs"); - console.error("\nTo create a token:"); - console.error(" 1. Go to https://github.com/settings/tokens"); - console.error(" 2. Create a token with 'repo' or 'public_repo' scope"); + core.setFailed( + "āŒ Error: No GitHub token found\n" + + "Please set GITHUB_TOKEN or GH_TOKEN environment variable\n" + + "\nExample:\n" + + " GITHUB_TOKEN=ghp_xxx node test-live-github-api.cjs\n" + + "\nTo create a token:\n" + + " 1. Go to https://github.com/settings/tokens\n" + + " 2. Create a token with 'repo' or 'public_repo' scope" + ); process.exit(1); } @@ -71,9 +73,7 @@ async function testLiveGitHubAPI() { if (hash === hash2) { core.info(`āœ… Hashes match - computation is deterministic`); } else { - console.error(`āŒ Error: Hashes don't match!`); - console.error(` First: ${hash}`); - console.error(` Second: ${hash2}`); + core.setFailed(`āŒ Error: Hashes don't match!\n First: ${hash}\n Second: ${hash2}`); process.exit(1); } @@ -86,17 +86,18 @@ async function testLiveGitHubAPI() { core.info(`\n✨ All tests passed! The JavaScript implementation works correctly with GitHub API.`); } catch (err) { const error = err; - console.error(`\nāŒ Error: ${getErrorMessage(error)}`); + let msg = `\nāŒ Error: ${getErrorMessage(error)}`; if (error && typeof error === "object" && "status" in error) { const statusError = error; if (statusError.status === 401) { - console.error(" Authentication failed - check your GitHub token"); + msg += "\n Authentication failed - check your GitHub token"; } else if (statusError.status === 404) { - console.error(" File not found - check repository and file path"); + msg += "\n File not found - check repository and file path"; } else if (statusError.status === 403) { - console.error(" Rate limit exceeded or insufficient permissions"); + msg += "\n Rate limit exceeded or insufficient permissions"; } } + core.setFailed(msg); process.exit(1); } }