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_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.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/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/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
 /// <reference types="@actions/github-script" />
+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/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)");
   });
 });
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
 /// <reference types="@actions/github-script" />
 "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..853acf34714 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
@@ -19,17 +20,19 @@ 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);
   }
 
-  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,65 +40,64 @@ 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}`);
-      console.error(`   Second: ${hash2}`);
+      core.setFailed(`❌ Error: Hashes don't match!\n   First:  ${hash}\n   Second: ${hash2}`);
       process.exit(1);
     }
 
     // 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)}`);
+    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);
   }
 }
