Skip to content

[aw] PR Code Quality Reviewer failed #50380

Description

@github-actions

Workflow Failure

Workflow: PR Code Quality Reviewer
Branch: copilot/deep-report-add-items-succeeded-failed-signal
Run: https://github.com/github/gh-aw/actions/runs/30943794959
Pull Request: #50371

Warning

Engine Failure: The copilot engine terminated unexpectedly.

Last agent output:

{"type":"tool.execution_complete","timestamp":"2026-08-04T19:38:07.706Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"{\"additions\":146,\"body\":\"`safe_outputs` batches could persist many items but still appear as a flat failure when one item failed. This adds item-level status signals so consumers can distinguish partial success from total failure.\\n\\n- **Runtime outputs**\\n  - Emit `items_succeeded`, `items_failed`, and `status` from the `process_safe_outputs` step.\\n  - `status` is one of `success`, `partial_success`, or `failure`.\\n\\n- **Job-level signals**\\n  - Expose the new step outputs through the `safe_outputs` job as:\\n    - `process_safe_outputs_items_succeeded`\\n    - `process_safe_outputs_items_failed`\\n    - `process_safe_outputs_status`\\n\\n- **Step summary**\\n  - Include the same item counts and status in the Safe Output Processing Summary.\\n\\nExample output shape:\\n\\n```yaml\\nneeds.safe_outputs.outputs.process_safe_outputs_items_succeeded: \\\"10\\\"\\nneeds.safe_outputs.outputs.process_safe_outputs_items_failed: \\\"5\\\"\\nneeds.safe_outputs.outputs.process_safe_outputs_status: \\\"partial_success\\\"\\n```\\n\\n<!-- START COPILOT CODING AGENT SUFFIX -->\\n\\n- Fixes #50301\",\"changedFiles\":8,\"deletions\":13,\"files\":[{\"path\":\".github/skills/agentic-workflows/SKILL.md\",\"additions\":0,\"deletions\":1,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_handler_manager.cjs\",\"additions\":28,\"deletions\":12,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_handler_manager.test.cjs\",\"additions\":44,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_summary.cjs\",\"additions\":5,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_summary.test.cjs\",\"additions\":29,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_outputs_status.cjs\",\"additions\":31,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/workflow/compiler_safe_outputs_job.go\",\"additions\":3,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/workflow/compiler_safe_outputs_job_test.go\",\"additions\":6,\"deletions\":0,\"changeType\":\"MODIFIED\"}],\"headRefName\":\"copilot/deep-report-add-items-succeeded-failed-signal\",\"headRefOid\":\"5d6b8761e4329eb9e156e031e7e48be205ed1e4c\",\"number\":50371,\"title\":\"Expose safe output partial-batch status counts\"}\n---\n325 /tmp/gh-aw/agent/pr-diff.patch\n---\n[]\n<shellId: 6 completed with exit code 0>","detailedContent":"{\"additions\":146,\"body\":\"`safe_outputs` batches could persist many items but still appear as a flat failure when one item failed. This adds item-level status signals so consumers can distinguish partial success from total failure.\\n\\n- **Runtime outputs**\\n  - Emit `items_succeeded`, `items_failed`, and `status` from the `process_safe_outputs` step.\\n  - `status` is one of `success`, `partial_success`, or `failure`.\\n\\n- **Job-level signals**\\n  - Expose the new step outputs through the `safe_outputs` job as:\\n    - `process_safe_outputs_items_succeeded`\\n    - `process_safe_outputs_items_failed`\\n    - `process_safe_outputs_status`\\n\\n- **Step summary**\\n  - Include the same item counts and status in the Safe Output Processing Summary.\\n\\nExample output shape:\\n\\n```yaml\\nneeds.safe_outputs.outputs.process_safe_outputs_items_succeeded: \\\"10\\\"\\nneeds.safe_outputs.outputs.process_safe_outputs_items_failed: \\\"5\\\"\\nneeds.safe_outputs.outputs.process_safe_outputs_status: \\\"partial_success\\\"\\n```\\n\\n<!-- START COPILOT CODING AGENT SUFFIX -->\\n\\n- Fixes #50301\",\"changedFiles\":8,\"deletions\":13,\"files\":[{\"path\":\".github/skills/agentic-workflows/SKILL.md\",\"additions\":0,\"deletions\":1,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_handler_manager.cjs\",\"additions\":28,\"deletions\":12,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_handler_manager.test.cjs\",\"additions\":44,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_summary.cjs\",\"additions\":5,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_output_summary.test.cjs\",\"additions\":29,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"actions/setup/js/safe_outputs_status.cjs\",\"additions\":31,\"deletions\":0,\"changeType\":\"ADDED\"},{\"path\":\"pkg/workflow/compiler_safe_outputs_job.go\",\"additions\":3,\"deletions\":0,\"changeType\":\"MODIFIED\"},{\"path\":\"pkg/workflow/compiler_safe_outputs_job_test.go\",\"additions\":6,\"deletions\":0,\"changeType\":\"MODIFIED\"}],\"headRefName\":\"copilot/deep-report-add-items-succeeded-failed-signal\",\"headRefOid\":\"5d6b8761e4329eb9e156e031e7e48be205ed1e4c\",\"number\":50371,\"title\":\"Expose safe output partial-batch status counts\"}\n---\n325 /tmp/gh-aw/agent/pr-diff.patch\n---\n[]\n<shellId: 6 completed with exit code 0>"}}}
{"type":"assistant.message","timestamp":"2026-08-04T19:38:10.245Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-04T19:38:10.247Z","data":{"toolName":"bash","mcpServerName":"","command":"cat /tmp/gh-aw/agent/pr-diff.patch"}}
{"type":"tool.execution_complete","timestamp":"2026-08-04T19:38:10.319Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md\nindex e9f1125c621..f11eaadd2e7 100644\n--- a/.github/skills/agentic-workflows/SKILL.md\n+++ b/.github/skills/agentic-workflows/SKILL.md\n@@ -98,6 +98,5 @@ After loading the matching workflow prompt or skill, follow it directly:\n - Choose workflow architecture and patterns: `.github/aw/patterns.md`\n - Optimize token usage and cost: `.github/aw/token-optimization.md`\n - Design long-running multi-agent research workflows: `.github/aw/multi-agent-research.md`\n-- Maintaining a repository, consuming backlog: `.githbub/aw/maintainer.md`\n \n When the task involves OTEL, OTLP, traces, observability backends, or telemetry-driven analysis, also read and follow `skills/otel-queries/SKILL.md` after loading the matching workflow prompt or skill.\ndiff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs\nindex 3164ca7ab1f..ca152ba02da 100644\n--- a/actions/setup/js/safe_output_handler_manager.cjs\n+++ b/actions/setup/js/safe_output_handler_manager.cjs\n@@ -12,6 +12,7 @@\n const { loadAgentOutput } = require(\"./load_agent_output.cjs\");\n const { getErrorMessage } = require(\"./error_helpers.cjs\");\n const { ERR_CONFIG, ERR_PARSE, ERR_VALIDATION } = require(\"./error_codes.cjs\");\n+const { computeSafeOutputsStatus, isFailedProcessingResult } = require(\"./safe_outputs_status.cjs\");\n const { hasUnresolvedTemporaryIds, replaceTemporaryIdReferences, replaceArtifactUrlReferences, normalizeTemporaryId } = require(\"./temporary_id.cjs\");\n const { generateMissingInfoSections } = require(\"./missing_info_formatter.cjs\");\n const { setCollectedMissings } = require(\"./missing_messages_helper.cjs\");\n@@ -657,16 +658,6 @@ function skipReviewResultsForPR(results, repo, prNumber, skipReason) {\n   }\n }\n \n-/**\n- * Determine whether a processing result is a non-skipped, non-deferred, non-cancelled failure.\n- *\n- * @param {{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}|null|undefined} result\n- * @returns {boolean}\n- */\n-function isFailedProcessingResult(result) {\n-  return Boolean(result?.success === false && !result?.deferred && !result?.skipped && !result?.cancelled);\n-}\n-\n /** Types whose failures are surfaced as warnings rather than failing the safe_outputs job. */\n const REPORT_ONLY_FAILURE_TYPES = new Set([\"assign_to_agent\", \"upload_artifact\"]);\n \n@@ -697,6 +688,17 @@ function partitionFailureResults(results) {\n   return { fatalFailures, reportOnlyFailures };\n }\n \n+/**\n+ * Export item-level safe-output status as GitHub Actions outputs.\n+ *\n+ * @param {{itemsSucceeded: number, itemsFailed: number, status: string}} status\n+ */\n+function setSafeOutputsStatusOutputs(status) {\n+  core.setOutput(\"items_succeeded\", String(status.itemsSucceeded));\n+  core.setOutput(\"items_failed\", String(status.itemsFailed));\n+  core.setOutput(\"status\", status.status);\n+}\n+\n /**\n  * Process all messages from agent output in the order they appear\n  * Dispatches each message to the appropriate handler while maintaining shared state (temporary ID map)\n@@ -1459,6 +1461,7 @@ async function main() {\n   const isStaged = isStagedMode();\n   /** @type {string | null} */\n   let failedOutputsMessage = null;\n+  let statusOutputsSet = false;\n \n   try {\n     core.info(\"Safe Output Handler Manager starting...\");\n@@ -1483,6 +1486,8 @@ async function main() {\n       if (!isStaged) ensureManifestExists();\n       core.setOutput(\"temporary_id_map\", \"{}\");\n       core.setOutput(\"processed_count\", \"0\");\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"success\" });\n+      statusOutputsSet = true;\n       return;\n     }\n \n@@ -1515,6 +1520,8 @@ async function main() {\n       // Set empty outputs for downstream steps\n       core.setOutput(\"temporary_id_map\", \"{}\");\n       core.setOutput(\"processed_count\", \"0\");\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"success\" });\n+      statusOutputsSet = true;\n       return;\n     }\n \n@@ -1592,7 +1599,8 @@ async function main() {\n     await writeSafeOutputSummaries(processingResult.results, allMessages);\n \n     // Log summary\n-    const successCount = processingResult.results.filter(r => r.success).length;\n+    const safeOutputsStatus = computeSafeOutputsStatus(processingResult.results);\n+    const successCount = safeOutputsStatus.itemsSucceeded;\n     const { fatalFailures, reportOnlyFailures } = partitionFailureResults(processingResult.results);\n     const failureCount = fatalFailures.length;\n     const reportOnlyFailureCount = reportOnlyFailures.length;\n@@ -1605,8 +1613,9 @@ async function main() {\n \n     core.info(`\\n=== Processing Summary ===`);\n     core.info(`Total messages: ${processingResult.results.length}`);\n+    core.info(`Status: ${safeOutputsStatus.status}`);\n     core.info(`Successful: ${successCount}`);\n-    core.info(`Failed: ${failureCount}`);\n+    core.info(`Failed: ${safeOutputsStatus.itemsFailed}`);\n     if (reportOnlyFailureCount > 0) {\n       core.info(`Reported assignment failures: ${reportOnlyFailureCount}`);\n     }\n@@ -1671,6 +1680,8 @@ async function main() {\n \n     // Export processed count for consistency with project handler\n     core.setOutput(\"processed_count\", String(successCount));\n+    setSafeOutputsStatusOutputs(safeOutputsStatus);\n+    statusOutputsSet = true;\n \n     // Export assign_to_agent outputs when the handler was loaded\n     if (messageHandlers.has(\"assign_to_agent\")) {\n@@ -1748,6 +1759,9 @@ async function main() {\n     core.info(\"Safe Output Handler Manager completed\");\n   } catch (error) {\n     const handlerError = `${ERR_VALIDATION}: Handler manager failed: ${getErrorMessage(error)}`;\n+    if (!statusOutputsSet) {\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"failure\" });\n+    }\n     if (failedOutputsMessage !== null) {\n       core.setFailed(`${failedOutputsMessage}\\n${handlerError}`);\n       return;\n@@ -1780,4 +1794,6 @@ module.exports = {\n   isFailedProcessingResult,\n   isReportOnlyFailureResult,\n   partitionFailureResults,\n+  computeSafeOutputsStatus,\n+  setSafeOutputsStatusOutputs,\n };diff --git a/actions/setup/js/safe_output_handler_manager.test.cjs b/actions/setup/js/safe_output_handler_manager.test.cjs\nindex 3e138408451..cf32b6d1b53 100644\n--- a/actions/setup/js/safe_output_handler_manager.test.cjs\n+++ b/actions/setup/js/safe_output_handler_manager.test.cjs\n@@ -16,6 +16,8 @@ import {\n   isFailedProcessingResult,\n   isReportOnlyFailureResult,\n   partitionFailureResults,\n+  computeSafeOutputsStatus,\n+  setSafeOutputsStatusOutputs,\n } from \"./safe_output_handler_manager.cjs\";\n \n const require = createRequire(import.meta.url);\n@@ -205,6 +207,48 @@ describe(\"Safe Output Handler Manager\", () => {\n       expect(fatalFailures).toEqual([{ type: \"create_issue\", success: false, error: \"Validation failed\" }]);\n     });\n \n+    it(\"computes partial success item status from mixed successful and failed results\", () => {\n+      const status = computeSafeOutputsStatus([\n+        { type: \"create_issue\", success: true },\n+        { type: \"add_comment\", success: true },\n+        { type: \"create_discussion\", success: false, error: \"Validation failed\" },\n+        { type: \"noop\", success: false, skipped: true },\n+        { type: \"link_sub_issue\", success: false, deferred: true },\n+        { type: \"merge_pull_request\", success: false, cancelled: true },\n+      ]);\n+\n+      expect(status).toEqual({\n+        itemsSucceeded: 2,\n+        itemsFailed: 1,\n+        status: \"partial_success\",\n+      });\n+    });\n+\n+    it(\"computes failure item status when all active results failed\", () => {\n+      expect(\n+        computeSafeOutputsStatus([\n+          { type: \"create_issue\", success: false, error: \"Validation failed\" },\n+          { type: \"add_comment\", success: false, error: \"Validation failed\" },\n+        ])\n+      ).toEqual({\n+        itemsSucceeded: 0,\n+        itemsFailed: 2,\n+        status: \"failure\",\n+      });\n+    });\n+\n+    it(\"exports item status outputs\", () => {\n+      setSafeOutputsStatusOutputs({\n+        itemsSucceeded: 10,\n+        itemsFailed: 5,\n+        status: \"partial_success\",\n+      });\n+\n+      expect(core.setOutput).toHaveBeenCalledWith(\"items_succeeded\", \"10\");\n+      expect(core.setOutput).toHaveBeenCalledWith(\"items_failed\", \"5\");\n+      expect(core.setOutput).toHaveBeenCalledWith(\"status\", \"partial_success\");\n+    });\n+\n     it(\"keeps review cleanup failures fatal unless a handler marks them skipped\", () => {\n       const { fatalFailures, reportOnlyFailures } = partitionFailureResults([\n         { type: \"resolve_pull_request_review_thread\", success: false, error: \"wrong node type\" },diff --git a/actions/setup/js/safe_output_summary.cjs b/actions/setup/js/safe_output_summary.cjs\nindex 600c60db937..86c9b6ac657 100644\n--- a/actions/setup/js/safe_output_summary.cjs\n+++ b/actions/setup/js/safe_output_summary.cjs\n@@ -10,6 +10,7 @@\n \n const { displayFileContent } = require(\"./display_file_helpers.cjs\");\n const { getErrorMessage } = require(\"./error_helpers.cjs\");\n+const { computeSafeOutputsStatus } = require(\"./safe_outputs_status.cjs\");\n \n /**\n  * Generate a step summary for a single safe-output message\n@@ -196,6 +197,10 @@ async function writeSafeOutputSummaries(results, messages) {\n \n   let summaryContent = `## Safe Output Processing Summary\\n\\n`;\n   summaryContent += `Processed ${results.length} safe-output message(s).\\n\\n`;\n+  const status = computeSafeOutputsStatus(results);\n+  summaryContent += `Status: **${status.status}**\\n\\n`;\n+  summaryContent += `Items succeeded: **${status.itemsSucceeded}**\\n\\n`;\n+  summaryContent += `Items failed: **${status.itemsFailed}**\\n\\n`;\n \n   // Generate summary for each result\n   for (const result of results) {diff --git a/actions/setup/js/safe_output_summary.test.cjs b/actions/setup/js/safe_output_summary.test.cjs\nindex 5af93f6ab25..42dc5e0b3b1 100644\n--- a/actions/setup/js/safe_output_summary.test.cjs\n+++ b/actions/setup/js/safe_output_summary.test.cjs\n@@ -541,10 +541,39 @@ describe(\"safe_output_summary\", () => {\n       const summaryContent = mockCore.summary.addRaw.mock.calls[0][0];\n       expect(summaryContent).toContain(\"Safe Output Processing Summary\");\n       expect(summaryContent).toContain(\"Processed 2 safe-output message(s)\");\n+      expect(summaryContent).toContain(\"Status: **success**\");\n+      expect(summaryContent).toContain(\"Items succeeded: **2**\");\n+      expect(summaryContent).toContain(\"Items failed: **0**\");\n       expect(summaryContent).toContain(\"Create Issue\");\n       expect(summaryContent).toContain(\"Create Project\");\n     });\n \n+    it(\"should include partial success item counts in the summary\", async () => {\n+      const results = [\n+        {\n+          type: \"create_issue\",\n+          messageIndex: 0,\n+          success: true,\n+          result: { repo: \"owner/repo\", number: 123 },\n+        },\n+        {\n+          type: \"create_discussion\",\n+          messageIndex: 1,\n+          success: false,\n+          error: \"Validation failed\",\n+        },\n+      ];\n+\n+      const messages = [{ title: \"Issue 1\" }, { title: \"Discussion 1\" }];\n+\n+      await writeSafeOutputSummaries(results, messages);\n+\n+      const summaryContent = mockCore.summary.addRaw.mock.calls[0][0];\n+      expect(summaryContent).toContain(\"Status: **partial_success**\");\n+      expect(summaryContent).toContain(\"Items succeeded: **1**\");\n+      expect(summaryContent).toContain(\"Items failed: **1**\");\n+    });\n+\n     it(\"should skip results handled by standalone steps\", async () => {\n       const results = [\n         {diff --git a/actions/setup/js/safe_outputs_status.cjs b/actions/setup/js/safe_outputs_status.cjs\nnew file mode 100644\nindex 00000000000..ce2ca35567a\n--- /dev/null\n+++ b/actions/setup/js/safe_outputs_status.cjs\n@@ -0,0 +1,31 @@\n+// @ts-check\n+\n+/**\n+ * Determine whether a processing result is a non-skipped, non-deferred, non-cancelled failure.\n+ *\n+ * @param {{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}|null|undefined} result\n+ * @returns {boolean}\n+ */\n+function isFailedProcessingResult(result) {\n+  return Boolean(result?.success === false && !result?.deferred && !result?.skipped && !result?.cancelled);\n+}\n+\n+/**\n+ * Compute item-level safe-output status for logs, step summary, and GitHub Actions outputs.\n+ *\n+ * @param {Array<{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}>|null|undefined} results\n+ * @returns {{itemsSucceeded: number, itemsFailed: number, status: \"success\" | \"partial_success\" | \"failure\"}}\n+ */\n+function computeSafeOutputsStatus(results) {\n+  const safeResults = Array.isArray(results) ? results : [];\n+  const itemsSucceeded = safeResults.filter(r => r?.success).length;\n+  const itemsFailed = safeResults.filter(isFailedProcessingResult).length;\n+  const status = itemsFailed === 0 ? \"success\" : itemsSucceeded > 0 ? \"partial_success\" : \"failure\";\n+\n+  return { itemsSucceeded, itemsFailed, status };\n+}\n+\n+module.exports = {\n+  computeSafeOutputsStatus,\n+  isFailedProcessingResult,\n+};diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go\nindex f1df94e1042..975f1dd37e2 100644\n--- a/pkg/workflow/compiler_safe_outputs_job.go\n+++ b/pkg/workflow/compiler_safe_outputs_job.go\n@@ -368,6 +368,9 @@ func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowDa\n \t\t// Add outputs from handler manager\n \t\toutputs[\"process_safe_outputs_temporary_id_map\"] = \"${{ steps.process_safe_outputs.outputs.temporary_id_map }}\"\n \t\toutputs[\"process_safe_outputs_processed_count\"] = \"${{ steps.process_safe_outputs.outputs.processed_count }}\"\n+\t\toutputs[\"process_safe_outputs_items_succeeded\"] = \"${{ steps.process_safe_outputs.outputs.items_succeeded }}\"\n+\t\toutputs[\"process_safe_outputs_items_failed\"] = \"${{ steps.process_safe_outputs.outputs.items_failed }}\"\n+\t\toutputs[\"process_safe_outputs_status\"] = \"${{ steps.process_safe_outputs.outputs.status }}\"\n \t\toutputs[\"create_discussion_errors\"] = \"${{ steps.process_safe_outputs.outputs.create_discussion_errors }}\"\n \t\toutputs[\"create_discussion_error_count\"] = \"${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}\"\n \t\toutputs[\"code_push_failure_errors\"] = \"${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}\"diff --git a/pkg/workflow/compiler_safe_outputs_job_test.go b/pkg/workflow/compiler_safe_outputs_job_test.go\nindex e5d9604d704..b14196d9c08 100644\n--- a/pkg/workflow/compiler_safe_outputs_job_test.go\n+++ b/pkg/workflow/compiler_safe_outputs_job_test.go\n@@ -929,9 +929,15 @@ func TestJobOutputs(t *testing.T) {\n \t// Handler manager outputs\n \tassert.Contains(t, job.Outputs, \"process_safe_outputs_temporary_id_map\")\n \tassert.Contains(t, job.Outputs, \"process_safe_outputs_processed_count\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_items_succeeded\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_items_failed\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_status\")\n \n \t// Check output format\n \tassert.Contains(t, job.Outputs[\"process_safe_outputs_temporary_id_map\"], \"steps.process_safe_outputs.outputs\")\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.items_succeeded }}\", job.Outputs[\"process_safe_outputs_items_succeeded\"])\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.items_failed }}\", job.Outputs[\"process_safe_outputs_items_failed\"])\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.status }}\", job.Outputs[\"process_safe_outputs_status\"])\n }\n \n // TestJobDependencies tests that job dependencies are correctly set\n<shellId: 7 completed with exit code 0>","detailedContent":"diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md\nindex e9f1125c621..f11eaadd2e7 100644\n--- a/.github/skills/agentic-workflows/SKILL.md\n+++ b/.github/skills/agentic-workflows/SKILL.md\n@@ -98,6 +98,5 @@ After loading the matching workflow prompt or skill, follow it directly:\n - Choose workflow architecture and patterns: `.github/aw/patterns.md`\n - Optimize token usage and cost: `.github/aw/token-optimization.md`\n - Design long-running multi-agent research workflows: `.github/aw/multi-agent-research.md`\n-- Maintaining a repository, consuming backlog: `.githbub/aw/maintainer.md`\n \n When the task involves OTEL, OTLP, traces, observability backends, or telemetry-driven analysis, also read and follow `skills/otel-queries/SKILL.md` after loading the matching workflow prompt or skill.\ndiff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs\nindex 3164ca7ab1f..ca152ba02da 100644\n--- a/actions/setup/js/safe_output_handler_manager.cjs\n+++ b/actions/setup/js/safe_output_handler_manager.cjs\n@@ -12,6 +12,7 @@\n const { loadAgentOutput } = require(\"./load_agent_output.cjs\");\n const { getErrorMessage } = require(\"./error_helpers.cjs\");\n const { ERR_CONFIG, ERR_PARSE, ERR_VALIDATION } = require(\"./error_codes.cjs\");\n+const { computeSafeOutputsStatus, isFailedProcessingResult } = require(\"./safe_outputs_status.cjs\");\n const { hasUnresolvedTemporaryIds, replaceTemporaryIdReferences, replaceArtifactUrlReferences, normalizeTemporaryId } = require(\"./temporary_id.cjs\");\n const { generateMissingInfoSections } = require(\"./missing_info_formatter.cjs\");\n const { setCollectedMissings } = require(\"./missing_messages_helper.cjs\");\n@@ -657,16 +658,6 @@ function skipReviewResultsForPR(results, repo, prNumber, skipReason) {\n   }\n }\n \n-/**\n- * Determine whether a processing result is a non-skipped, non-deferred, non-cancelled failure.\n- *\n- * @param {{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}|null|undefined} result\n- * @returns {boolean}\n- */\n-function isFailedProcessingResult(result) {\n-  return Boolean(result?.success === false && !result?.deferred && !result?.skipped && !result?.cancelled);\n-}\n-\n /** Types whose failures are surfaced as warnings rather than failing the safe_outputs job. */\n const REPORT_ONLY_FAILURE_TYPES = new Set([\"assign_to_agent\", \"upload_artifact\"]);\n \n@@ -697,6 +688,17 @@ function partitionFailureResults(results) {\n   return { fatalFailures, reportOnlyFailures };\n }\n \n+/**\n+ * Export item-level safe-output status as GitHub Actions outputs.\n+ *\n+ * @param {{itemsSucceeded: number, itemsFailed: number, status: string}} status\n+ */\n+function setSafeOutputsStatusOutputs(status) {\n+  core.setOutput(\"items_succeeded\", String(status.itemsSucceeded));\n+  core.setOutput(\"items_failed\", String(status.itemsFailed));\n+  core.setOutput(\"status\", status.status);\n+}\n+\n /**\n  * Process all messages from agent output in the order they appear\n  * Dispatches each message to the appropriate handler while maintaining shared state (temporary ID map)\n@@ -1459,6 +1461,7 @@ async function main() {\n   const isStaged = isStagedMode();\n   /** @type {string | null} */\n   let failedOutputsMessage = null;\n+  let statusOutputsSet = false;\n \n   try {\n     core.info(\"Safe Output Handler Manager starting...\");\n@@ -1483,6 +1486,8 @@ async function main() {\n       if (!isStaged) ensureManifestExists();\n       core.setOutput(\"temporary_id_map\", \"{}\");\n       core.setOutput(\"processed_count\", \"0\");\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"success\" });\n+      statusOutputsSet = true;\n       return;\n     }\n \n@@ -1515,6 +1520,8 @@ async function main() {\n       // Set empty outputs for downstream steps\n       core.setOutput(\"temporary_id_map\", \"{}\");\n       core.setOutput(\"processed_count\", \"0\");\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"success\" });\n+      statusOutputsSet = true;\n       return;\n     }\n \n@@ -1592,7 +1599,8 @@ async function main() {\n     await writeSafeOutputSummaries(processingResult.results, allMessages);\n \n     // Log summary\n-    const successCount = processingResult.results.filter(r => r.success).length;\n+    const safeOutputsStatus = computeSafeOutputsStatus(processingResult.results);\n+    const successCount = safeOutputsStatus.itemsSucceeded;\n     const { fatalFailures, reportOnlyFailures } = partitionFailureResults(processingResult.results);\n     const failureCount = fatalFailures.length;\n     const reportOnlyFailureCount = reportOnlyFailures.length;\n@@ -1605,8 +1613,9 @@ async function main() {\n \n     core.info(`\\n=== Processing Summary ===`);\n     core.info(`Total messages: ${processingResult.results.length}`);\n+    core.info(`Status: ${safeOutputsStatus.status}`);\n     core.info(`Successful: ${successCount}`);\n-    core.info(`Failed: ${failureCount}`);\n+    core.info(`Failed: ${safeOutputsStatus.itemsFailed}`);\n     if (reportOnlyFailureCount > 0) {\n       core.info(`Reported assignment failures: ${reportOnlyFailureCount}`);\n     }\n@@ -1671,6 +1680,8 @@ async function main() {\n \n     // Export processed count for consistency with project handler\n     core.setOutput(\"processed_count\", String(successCount));\n+    setSafeOutputsStatusOutputs(safeOutputsStatus);\n+    statusOutputsSet = true;\n \n     // Export assign_to_agent outputs when the handler was loaded\n     if (messageHandlers.has(\"assign_to_agent\")) {\n@@ -1748,6 +1759,9 @@ async function main() {\n     core.info(\"Safe Output Handler Manager completed\");\n   } catch (error) {\n     const handlerError = `${ERR_VALIDATION}: Handler manager failed: ${getErrorMessage(error)}`;\n+    if (!statusOutputsSet) {\n+      setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"failure\" });\n+    }\n     if (failedOutputsMessage !== null) {\n       core.setFailed(`${failedOutputsMessage}\\n${handlerError}`);\n       return;\n@@ -1780,4 +1794,6 @@ module.exports = {\n   isFailedProcessingResult,\n   isReportOnlyFailureResult,\n   partitionFailureResults,\n+  computeSafeOutputsStatus,\n+  setSafeOutputsStatusOutputs,\n };diff --git a/actions/setup/js/safe_output_handler_manager.test.cjs b/actions/setup/js/safe_output_handler_manager.test.cjs\nindex 3e138408451..cf32b6d1b53 100644\n--- a/actions/setup/js/safe_output_handler_manager.test.cjs\n+++ b/actions/setup/js/safe_output_handler_manager.test.cjs\n@@ -16,6 +16,8 @@ import {\n   isFailedProcessingResult,\n   isReportOnlyFailureResult,\n   partitionFailureResults,\n+  computeSafeOutputsStatus,\n+  setSafeOutputsStatusOutputs,\n } from \"./safe_output_handler_manager.cjs\";\n \n const require = createRequire(import.meta.url);\n@@ -205,6 +207,48 @@ describe(\"Safe Output Handler Manager\", () => {\n       expect(fatalFailures).toEqual([{ type: \"create_issue\", success: false, error: \"Validation failed\" }]);\n     });\n \n+    it(\"computes partial success item status from mixed successful and failed results\", () => {\n+      const status = computeSafeOutputsStatus([\n+        { type: \"create_issue\", success: true },\n+        { type: \"add_comment\", success: true },\n+        { type: \"create_discussion\", success: false, error: \"Validation failed\" },\n+        { type: \"noop\", success: false, skipped: true },\n+        { type: \"link_sub_issue\", success: false, deferred: true },\n+        { type: \"merge_pull_request\", success: false, cancelled: true },\n+      ]);\n+\n+      expect(status).toEqual({\n+        itemsSucceeded: 2,\n+        itemsFailed: 1,\n+        status: \"partial_success\",\n+      });\n+    });\n+\n+    it(\"computes failure item status when all active results failed\", () => {\n+      expect(\n+        computeSafeOutputsStatus([\n+          { type: \"create_issue\", success: false, error: \"Validation failed\" },\n+          { type: \"add_comment\", success: false, error: \"Validation failed\" },\n+        ])\n+      ).toEqual({\n+        itemsSucceeded: 0,\n+        itemsFailed: 2,\n+        status: \"failure\",\n+      });\n+    });\n+\n+    it(\"exports item status outputs\", () => {\n+      setSafeOutputsStatusOutputs({\n+        itemsSucceeded: 10,\n+        itemsFailed: 5,\n+        status: \"partial_success\",\n+      });\n+\n+      expect(core.setOutput).toHaveBeenCalledWith(\"items_succeeded\", \"10\");\n+      expect(core.setOutput).toHaveBeenCalledWith(\"items_failed\", \"5\");\n+      expect(core.setOutput).toHaveBeenCalledWith(\"status\", \"partial_success\");\n+    });\n+\n     it(\"keeps review cleanup failures fatal unless a handler marks them skipped\", () => {\n       const { fatalFailures, reportOnlyFailures } = partitionFailureResults([\n         { type: \"resolve_pull_request_review_thread\", success: false, error: \"wrong node type\" },diff --git a/actions/setup/js/safe_output_summary.cjs b/actions/setup/js/safe_output_summary.cjs\nindex 600c60db937..86c9b6ac657 100644\n--- a/actions/setup/js/safe_output_summary.cjs\n+++ b/actions/setup/js/safe_output_summary.cjs\n@@ -10,6 +10,7 @@\n \n const { displayFileContent } = require(\"./display_file_helpers.cjs\");\n const { getErrorMessage } = require(\"./error_helpers.cjs\");\n+const { computeSafeOutputsStatus } = require(\"./safe_outputs_status.cjs\");\n \n /**\n  * Generate a step summary for a single safe-output message\n@@ -196,6 +197,10 @@ async function writeSafeOutputSummaries(results, messages) {\n \n   let summaryContent = `## Safe Output Processing Summary\\n\\n`;\n   summaryContent += `Processed ${results.length} safe-output message(s).\\n\\n`;\n+  const status = computeSafeOutputsStatus(results);\n+  summaryContent += `Status: **${status.status}**\\n\\n`;\n+  summaryContent += `Items succeeded: **${status.itemsSucceeded}**\\n\\n`;\n+  summaryContent += `Items failed: **${status.itemsFailed}**\\n\\n`;\n \n   // Generate summary for each result\n   for (const result of results) {diff --git a/actions/setup/js/safe_output_summary.test.cjs b/actions/setup/js/safe_output_summary.test.cjs\nindex 5af93f6ab25..42dc5e0b3b1 100644\n--- a/actions/setup/js/safe_output_summary.test.cjs\n+++ b/actions/setup/js/safe_output_summary.test.cjs\n@@ -541,10 +541,39 @@ describe(\"safe_output_summary\", () => {\n       const summaryContent = mockCore.summary.addRaw.mock.calls[0][0];\n       expect(summaryContent).toContain(\"Safe Output Processing Summary\");\n       expect(summaryContent).toContain(\"Processed 2 safe-output message(s)\");\n+      expect(summaryContent).toContain(\"Status: **success**\");\n+      expect(summaryContent).toContain(\"Items succeeded: **2**\");\n+      expect(summaryContent).toContain(\"Items failed: **0**\");\n       expect(summaryContent).toContain(\"Create Issue\");\n       expect(summaryContent).toContain(\"Create Project\");\n     });\n \n+    it(\"should include partial success item counts in the summary\", async () => {\n+      const results = [\n+        {\n+          type: \"create_issue\",\n+          messageIndex: 0,\n+          success: true,\n+          result: { repo: \"owner/repo\", number: 123 },\n+        },\n+        {\n+          type: \"create_discussion\",\n+          messageIndex: 1,\n+          success: false,\n+          error: \"Validation failed\",\n+        },\n+      ];\n+\n+      const messages = [{ title: \"Issue 1\" }, { title: \"Discussion 1\" }];\n+\n+      await writeSafeOutputSummaries(results, messages);\n+\n+      const summaryContent = mockCore.summary.addRaw.mock.calls[0][0];\n+      expect(summaryContent).toContain(\"Status: **partial_success**\");\n+      expect(summaryContent).toContain(\"Items succeeded: **1**\");\n+      expect(summaryContent).toContain(\"Items failed: **1**\");\n+    });\n+\n     it(\"should skip results handled by standalone steps\", async () => {\n       const results = [\n         {diff --git a/actions/setup/js/safe_outputs_status.cjs b/actions/setup/js/safe_outputs_status.cjs\nnew file mode 100644\nindex 00000000000..ce2ca35567a\n--- /dev/null\n+++ b/actions/setup/js/safe_outputs_status.cjs\n@@ -0,0 +1,31 @@\n+// @ts-check\n+\n+/**\n+ * Determine whether a processing result is a non-skipped, non-deferred, non-cancelled failure.\n+ *\n+ * @param {{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}|null|undefined} result\n+ * @returns {boolean}\n+ */\n+function isFailedProcessingResult(result) {\n+  return Boolean(result?.success === false && !result?.deferred && !result?.skipped && !result?.cancelled);\n+}\n+\n+/**\n+ * Compute item-level safe-output status for logs, step summary, and GitHub Actions outputs.\n+ *\n+ * @param {Array<{success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}>|null|undefined} results\n+ * @returns {{itemsSucceeded: number, itemsFailed: number, status: \"success\" | \"partial_success\" | \"failure\"}}\n+ */\n+function computeSafeOutputsStatus(results) {\n+  const safeResults = Array.isArray(results) ? results : [];\n+  const itemsSucceeded = safeResults.filter(r => r?.success).length;\n+  const itemsFailed = safeResults.filter(isFailedProcessingResult).length;\n+  const status = itemsFailed === 0 ? \"success\" : itemsSucceeded > 0 ? \"partial_success\" : \"failure\";\n+\n+  return { itemsSucceeded, itemsFailed, status };\n+}\n+\n+module.exports = {\n+  computeSafeOutputsStatus,\n+  isFailedProcessingResult,\n+};diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go\nindex f1df94e1042..975f1dd37e2 100644\n--- a/pkg/workflow/compiler_safe_outputs_job.go\n+++ b/pkg/workflow/compiler_safe_outputs_job.go\n@@ -368,6 +368,9 @@ func (c *Compiler) buildSafeOutputsHandlerOutputsAndActionSteps(data *WorkflowDa\n \t\t// Add outputs from handler manager\n \t\toutputs[\"process_safe_outputs_temporary_id_map\"] = \"${{ steps.process_safe_outputs.outputs.temporary_id_map }}\"\n \t\toutputs[\"process_safe_outputs_processed_count\"] = \"${{ steps.process_safe_outputs.outputs.processed_count }}\"\n+\t\toutputs[\"process_safe_outputs_items_succeeded\"] = \"${{ steps.process_safe_outputs.outputs.items_succeeded }}\"\n+\t\toutputs[\"process_safe_outputs_items_failed\"] = \"${{ steps.process_safe_outputs.outputs.items_failed }}\"\n+\t\toutputs[\"process_safe_outputs_status\"] = \"${{ steps.process_safe_outputs.outputs.status }}\"\n \t\toutputs[\"create_discussion_errors\"] = \"${{ steps.process_safe_outputs.outputs.create_discussion_errors }}\"\n \t\toutputs[\"create_discussion_error_count\"] = \"${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}\"\n \t\toutputs[\"code_push_failure_errors\"] = \"${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}\"diff --git a/pkg/workflow/compiler_safe_outputs_job_test.go b/pkg/workflow/compiler_safe_outputs_job_test.go\nindex e5d9604d704..b14196d9c08 100644\n--- a/pkg/workflow/compiler_safe_outputs_job_test.go\n+++ b/pkg/workflow/compiler_safe_outputs_job_test.go\n@@ -929,9 +929,15 @@ func TestJobOutputs(t *testing.T) {\n \t// Handler manager outputs\n \tassert.Contains(t, job.Outputs, \"process_safe_outputs_temporary_id_map\")\n \tassert.Contains(t, job.Outputs, \"process_safe_outputs_processed_count\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_items_succeeded\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_items_failed\")\n+\tassert.Contains(t, job.Outputs, \"process_safe_outputs_status\")\n \n \t// Check output format\n \tassert.Contains(t, job.Outputs[\"process_safe_outputs_temporary_id_map\"], \"steps.process_safe_outputs.outputs\")\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.items_succeeded }}\", job.Outputs[\"process_safe_outputs_items_succeeded\"])\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.items_failed }}\", job.Outputs[\"process_safe_outputs_items_failed\"])\n+\tassert.Equal(t, \"${{ steps.process_safe_outputs.outputs.status }}\", job.Outputs[\"process_safe_outputs_status\"])\n }\n \n // TestJobDependencies tests that job dependencies are correctly set\n<shellId: 7 completed with exit code 0>"}}}
{"type":"assistant.message","timestamp":"2026-08-04T19:38:35.537Z","data":{"content":""}}
{"type":"tool.execution_start","timestamp":"2026-08-04T19:38:35.540Z","data":{"toolName":"task","mcpServerName":""}}
{"type":"subagent.started","timestamp":"2026-08-04T19:38:35.548Z","data":{"agentName":"general-purpose","agentDisplayName":"General Purpose Agent","toolCallId":"toolu_01JpjoXYQ31UpmSn3a5vf5mn"}}
{"type":"tool.execution_complete","timestamp":"2026-08-04T19:38:35.555Z","data":{"toolName":"task","mcpServerName":"","success":true,"result":{"content":"Agent started in background with agent_id: grumpy-coder-3. You'll be notified when it completes. Tell the user you're waiting and end your response, or continue unrelated work until notified. The agent supports multi-turn conversations — use write_agent to send follow-up messages.","detailedContent":"Prompt to general-purpose agent (grumpy-coder-3):\nYou are a highly critical, grumpy senior code reviewer doing first-pass issue mining on a GitHub PR diff. Be skeptical, look for correctness bugs, edge cases, race conditions, performance issues, and maintainability problems. ONLY look at changed lines in the diff below.\n\nPR: \"Expose safe output partial-batch status counts\" in github/gh-aw repo. It adds an `computeSafeOutputsStatus` function that classifies safe-output batch processing results as success/partial_success/failure based on item success/failure counts, exposes GitHub Actions outputs (items_succeeded, items_failed, status), and shows them in step summary and Go compiler job outputs.\n\nChanged files: .github/skills/agentic-workflows/SKILL.md, actions/setup/js/safe_output_handler_manager.cjs, actions/setup/js/safe_output_handler_manager.test.cjs, actions/setup/js/safe_output_summary.cjs, actions/setup/js/safe_output_summary.test.cjs, actions/setup/js/safe_outputs_status.cjs (NEW), pkg/workflow/compiler_safe_outputs_job.go, pkg/workflow/compiler_safe_outputs_job_test.go\n\nFull diff:\n```diff\n$(cat /tmp/gh-aw/agent/pr-diff.patch)\n```\n\nIMPORTANT CONTEXT: In the same file (safe_output_handler_manager.cjs), there is an existing function `partitionFailureResults` that splits failures into `fatalFailures` and `reportOnlyFailures` based on a `REPORT_ONLY_FAILURE_TYPES` set (types: \"assign_to_agent\", \"upload_artifact\") — these are failures that should NOT fail the overall job. The new `computeSafeOutputsStatus`/`isFailedProcessingResult` function does NOT exclude these report-only failure types when computing `itemsFailed` and `status`. Consider whether this creates an inconsistency where the new `status` output (and step summary) could report \"partial_success\" or \"failure\" even when the job actually succeeds (because only non-fatal report-only failures occurred, e.g. upload_artifact failing while everything else succeeds).\n\nAlso examine:\n- The catch-all error handler block: `if (!statusOutputsSet) { setSafeOutputsStatusOutputs({ itemsSucceeded: 0, itemsFailed: 0, status: \"failure\" }); }` — does hardcoding 0/0 lose useful partial info if processingResult.results existed before the exception?\n- Any duplicated logic between old `isFailedProcessingResult` (deleted from safe_output_handler_manager.cjs) and new module (safe_outputs_status.cjs) — check for consistency issues in re-export.\n- Test coverage adequacy: do new tests cover the report-only-failure-type interaction with computeSafeOutputsStatus? (They likely don't — check.)\n- Naming/type issues, magic strings (\"success\"/\"partial_success\"/\"failure\") repeated instead of using constants/enum.\n\nOutput STRICT JSONL, one finding per line, each line a JSON object with fields: `file`, `line` (best estimate of line number in the new file version from the diff hunk, or null), `severity` (critical/high/medium/low), `issue` (short description), `impact` (why it matters). Do not output anything except JSONL lines. If no issues found in a category, omit it — do not pad with low-value findings."}}}
[copilot-sdk-driver] [sdk-driver] error: Execution failed: Error: No model available. Check policy enablement under GitHub Settings > Copilot
{"type":"subagent.completed","timestamp":"2026-08-04T19:38:35.630Z","data":{"agentName":"general-purpose","toolCallId":"toolu_01JpjoXYQ31UpmSn3a5vf5mn"}}

Action Required

Assign this issue to an agent to debug and fix the issue.

Debug with any coding agent

Use this prompt with any coding agent (GitHub Copilot, Claude, Gemini, etc.):

Debug the agentic workflow failure using https://raw.githubusercontent.com/github/gh-aw/main/debug.md

The failed workflow run is at https://github.com/github/gh-aw/actions/runs/30943794959
Manually invoke the agent

Debug this workflow failure using your favorite Agent CLI and the agentic-workflows prompt.

Tip

Stop reporting this workflow as a failure

To stop a workflow from creating failure issues, set report-failure-as-issue: false in its frontmatter:

safe-outputs:
  report-failure-as-issue: false

Generated from PR Code Quality Reviewer · 50.5 AIC ·

  • expires on Aug 5, 2026, 7:42 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions