diff --git a/.github/workflows/breaking-change-checker.lock.yml b/.github/workflows/breaking-change-checker.lock.yml index 7f460bab47f..9fbbd169195 100644 --- a/.github/workflows/breaking-change-checker.lock.yml +++ b/.github/workflows/breaking-change-checker.lock.yml @@ -967,6 +967,8 @@ jobs: GH_AW_WORKFLOW_ID: "breaking-change-checker" GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_ASSIGN_COPILOT_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.assign_copilot_failure_count }} + GH_AW_ASSIGN_COPILOT_ERRORS: ${{ needs.safe_outputs.outputs.assign_copilot_errors }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e ⚠️ *Compatibility report by [{workflow_name}]({run_url})*{history_link}\",\"footerWorkflowRecompile\":\"\\u003e 🛠️ *Workflow maintenance by [{workflow_name}]({run_url}) for {repository}*\",\"runStarted\":\"🔬 Breaking Change Checker online! [{workflow_name}]({run_url}) is analyzing API compatibility on this {event_type}...\",\"runSuccess\":\"✅ Analysis complete! [{workflow_name}]({run_url}) has reviewed all changes. Compatibility verdict delivered! 📋\",\"runFailure\":\"🔬 Analysis interrupted! [{workflow_name}]({run_url}) {status}. Compatibility status unknown...\"}" GH_AW_GROUP_REPORTS: "false" @@ -1059,6 +1061,8 @@ jobs: GH_AW_WORKFLOW_ID: "breaking-change-checker" GH_AW_WORKFLOW_NAME: "Breaking Change Checker" outputs: + assign_copilot_errors: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_errors }} + assign_copilot_failure_count: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_failure_count }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} @@ -1118,7 +1122,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - name: Assign Copilot to created issues + id: assign_copilot_to_created_issues if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' + continue-on-error: true uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index b789f45d40e..c4f5f31e071 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -1238,6 +1238,8 @@ jobs: GH_AW_WORKFLOW_ID: "daily-doc-healer" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_ASSIGN_COPILOT_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.assign_copilot_failure_count }} + GH_AW_ASSIGN_COPILOT_ERRORS: ${{ needs.safe_outputs.outputs.assign_copilot_errors }} GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} @@ -1303,6 +1305,8 @@ jobs: GH_AW_WORKFLOW_ID: "daily-doc-healer" GH_AW_WORKFLOW_NAME: "Daily Documentation Healer" outputs: + assign_copilot_errors: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_errors }} + assign_copilot_failure_count: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_failure_count }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} @@ -1393,7 +1397,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - name: Assign Copilot to created issues + id: assign_copilot_to_created_issues if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' + continue-on-error: true uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 5efcd5abf13..5ad305ef73f 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -982,6 +982,8 @@ jobs: GH_AW_WORKFLOW_ID: "duplicate-code-detector" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_ASSIGN_COPILOT_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.assign_copilot_failure_count }} + GH_AW_ASSIGN_COPILOT_ERRORS: ${{ needs.safe_outputs.outputs.assign_copilot_errors }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" @@ -1025,6 +1027,8 @@ jobs: GH_AW_WORKFLOW_ID: "duplicate-code-detector" GH_AW_WORKFLOW_NAME: "Duplicate Code Detector" outputs: + assign_copilot_errors: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_errors }} + assign_copilot_failure_count: ${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_failure_count }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} @@ -1084,7 +1088,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - name: Assign Copilot to created issues + id: assign_copilot_to_created_issues if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != '' + continue-on-error: true uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }} diff --git a/actions/setup/js/assign_copilot_to_created_issues.cjs b/actions/setup/js/assign_copilot_to_created_issues.cjs index 05a61c639b2..4c900bad6af 100644 --- a/actions/setup/js/assign_copilot_to_created_issues.cjs +++ b/actions/setup/js/assign_copilot_to_created_issues.cjs @@ -163,9 +163,15 @@ async function main() { await core.summary.addRaw(summaryContent).write(); - // Fail if any assignments failed + // Set outputs for the conclusion job to report failures in the agent failure issue/comment + const assignCopilotErrors = failedResults.map(r => `issue:${r.issue_number}:copilot:${r.error}`).join("\n"); + core.setOutput("assign_copilot_failure_count", failureCount.toString()); + core.setOutput("assign_copilot_errors", assignCopilotErrors); + + // Warn instead of failing so the conclusion job can propagate the failure details + // to the agent failure issue/comment with a clear explanatory note if (failureCount > 0) { - core.setFailed(`${ERR_API}: Failed to assign copilot to ${failureCount} issue(s)`); + core.warning(`Failed to assign copilot to ${failureCount} issue(s) - errors will be reported in conclusion job`); } } diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 08a7173cb1a..28250400415 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -657,6 +657,35 @@ function buildLockdownCheckFailedContext(hasLockdownCheckFailed) { return "\n" + template; } +/** + * Build a context string when assigning the Copilot coding agent to created issues failed. + * @param {boolean} hasAssignCopilotFailures - Whether any copilot assignments failed + * @param {string} assignCopilotErrors - Newline-separated list of "issue:number:copilot:error" entries + * @returns {string} Formatted context string, or empty string if no failures + */ +function buildAssignCopilotFailureContext(hasAssignCopilotFailures, assignCopilotErrors) { + if (!hasAssignCopilotFailures) { + return ""; + } + + // Build a list of failed issue assignments + let issueList = ""; + if (assignCopilotErrors) { + const errorLines = assignCopilotErrors.split("\n").filter(line => line.trim()); + for (const errorLine of errorLines) { + const parts = errorLine.split(":"); + if (parts.length >= 4) { + const number = parts[1]; + const error = parts.slice(3).join(":"); // Rest is the error message + issueList += `- Issue #${number}: ${error}\n`; + } + } + } + + const templatePath = `${process.env.RUNNER_TEMP}/gh-aw/prompts/assign_copilot_to_created_issues_failure.md`; + return "\n" + renderTemplateFromFile(templatePath, { issues: issueList }); +} + /** * Handle agent job failure by creating or updating a failure tracking issue * This script is called from the conclusion job when the agent job has failed @@ -674,6 +703,8 @@ async function main() { const secretVerificationResult = process.env.GH_AW_SECRET_VERIFICATION_RESULT || ""; const assignmentErrors = process.env.GH_AW_ASSIGNMENT_ERRORS || ""; const assignmentErrorCount = process.env.GH_AW_ASSIGNMENT_ERROR_COUNT || "0"; + const assignCopilotErrors = process.env.GH_AW_ASSIGN_COPILOT_ERRORS || ""; + const assignCopilotFailureCount = process.env.GH_AW_ASSIGN_COPILOT_FAILURE_COUNT || "0"; const createDiscussionErrors = process.env.GH_AW_CREATE_DISCUSSION_ERRORS || ""; const createDiscussionErrorCount = process.env.GH_AW_CREATE_DISCUSSION_ERROR_COUNT || "0"; const codePushFailureErrors = process.env.GH_AW_CODE_PUSH_FAILURE_ERRORS || ""; @@ -719,6 +750,7 @@ async function main() { core.info(`Workflow ID: ${workflowID}`); core.info(`Secret verification result: ${secretVerificationResult}`); core.info(`Assignment error count: ${assignmentErrorCount}`); + core.info(`Assign copilot failure count: ${assignCopilotFailureCount}`); core.info(`Create discussion error count: ${createDiscussionErrorCount}`); core.info(`Code push failure count: ${codePushFailureCount}`); core.info(`Checkout PR success: ${checkoutPRSuccess}`); @@ -733,6 +765,9 @@ async function main() { // Check if there are assignment errors (regardless of agent job status) const hasAssignmentErrors = parseInt(assignmentErrorCount, 10) > 0; + // Check if there are copilot assignment failures for created issues (regardless of agent job status) + const hasAssignCopilotFailures = parseInt(assignCopilotFailureCount, 10) > 0; + // Check if there are create_discussion errors (regardless of agent job status) const hasCreateDiscussionErrors = parseInt(createDiscussionErrorCount, 10) > 0; @@ -764,12 +799,13 @@ async function main() { // Only proceed if the agent job actually failed OR timed out OR there are assignment errors OR // create_discussion errors OR code-push failures OR push_repo_memory failed OR missing safe outputs - // OR a GitHub App token minting step failed OR the lockdown check failed. + // OR a GitHub App token minting step failed OR the lockdown check failed OR copilot assignment failed. // BUT skip if we only have noop outputs (that's a successful no-action scenario) if ( agentConclusion !== "failure" && !isTimedOut && !hasAssignmentErrors && + !hasAssignCopilotFailures && !hasCreateDiscussionErrors && !hasCodePushFailures && !hasPushRepoMemoryFailure && @@ -947,6 +983,9 @@ async function main() { // Build lockdown check failure context const lockdownCheckFailedContext = buildLockdownCheckFailedContext(hasLockdownCheckFailed); + // Build copilot assignment failure context for created issues + const assignCopilotFailureContext = buildAssignCopilotFailureContext(hasAssignCopilotFailures, assignCopilotErrors); + // Create template context const templateContext = { run_url: runUrl, @@ -960,6 +999,7 @@ async function main() { ? "\n**⚠️ Secret Verification Failed**: The workflow's secret validation step failed. Please check that the required secrets are configured in your repository settings.\n\nFor more information on configuring tokens, see: https://github.github.com/gh-aw/reference/engines/\n" : "", assignment_errors_context: assignmentErrorsContext, + assign_copilot_failure_context: assignCopilotFailureContext, create_discussion_errors_context: createDiscussionErrorsContext, code_push_failure_context: codePushFailureContext, repo_memory_validation_context: repoMemoryValidationContext, @@ -1080,6 +1120,9 @@ async function main() { // Build lockdown check failure context const lockdownCheckFailedContext = buildLockdownCheckFailedContext(hasLockdownCheckFailed); + // Build copilot assignment failure context for created issues + const assignCopilotFailureContext = buildAssignCopilotFailureContext(hasAssignCopilotFailures, assignCopilotErrors); + // Create template context with sanitized workflow name const templateContext = { workflow_name: sanitizedWorkflowName, @@ -1094,6 +1137,7 @@ async function main() { ? "\n**⚠️ Secret Verification Failed**: The workflow's secret validation step failed. Please check that the required secrets are configured in your repository settings.\n\nFor more information on configuring tokens, see: https://github.github.com/gh-aw/reference/engines/\n" : "", assignment_errors_context: assignmentErrorsContext, + assign_copilot_failure_context: assignCopilotFailureContext, create_discussion_errors_context: createDiscussionErrorsContext, code_push_failure_context: codePushFailureContext, repo_memory_validation_context: repoMemoryValidationContext, @@ -1161,4 +1205,4 @@ async function main() { } } -module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext, buildTimeoutContext }; +module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext, buildTimeoutContext, buildAssignCopilotFailureContext }; diff --git a/actions/setup/md/agent_failure_comment.md b/actions/setup/md/agent_failure_comment.md index 14200c313dc..93653028460 100644 --- a/actions/setup/md/agent_failure_comment.md +++ b/actions/setup/md/agent_failure_comment.md @@ -1,3 +1,3 @@ Agent job [{run_id}]({run_url}) failed. -{secret_verification_context}{inference_access_error_context}{app_token_minting_failed_context}{lockdown_check_failed_context}{assignment_errors_context}{create_discussion_errors_context}{code_push_failure_context}{repo_memory_validation_context}{push_repo_memory_failure_context}{missing_data_context}{missing_safe_outputs_context}{timeout_context}{fork_context} +{secret_verification_context}{inference_access_error_context}{app_token_minting_failed_context}{lockdown_check_failed_context}{assignment_errors_context}{assign_copilot_failure_context}{create_discussion_errors_context}{code_push_failure_context}{repo_memory_validation_context}{push_repo_memory_failure_context}{missing_data_context}{missing_safe_outputs_context}{timeout_context}{fork_context} diff --git a/actions/setup/md/agent_failure_issue.md b/actions/setup/md/agent_failure_issue.md index 652713bfce8..df7805794cf 100644 --- a/actions/setup/md/agent_failure_issue.md +++ b/actions/setup/md/agent_failure_issue.md @@ -4,7 +4,7 @@ **Branch:** {branch} **Run:** {run_url}{pull_request_info} -{secret_verification_context}{inference_access_error_context}{app_token_minting_failed_context}{lockdown_check_failed_context}{assignment_errors_context}{create_discussion_errors_context}{code_push_failure_context}{repo_memory_validation_context}{push_repo_memory_failure_context}{missing_data_context}{missing_safe_outputs_context}{timeout_context}{fork_context} +{secret_verification_context}{inference_access_error_context}{app_token_minting_failed_context}{lockdown_check_failed_context}{assignment_errors_context}{assign_copilot_failure_context}{create_discussion_errors_context}{code_push_failure_context}{repo_memory_validation_context}{push_repo_memory_failure_context}{missing_data_context}{missing_safe_outputs_context}{timeout_context}{fork_context} ### Action Required diff --git a/actions/setup/md/assign_copilot_to_created_issues_failure.md b/actions/setup/md/assign_copilot_to_created_issues_failure.md new file mode 100644 index 00000000000..0ad5453aaf1 --- /dev/null +++ b/actions/setup/md/assign_copilot_to_created_issues_failure.md @@ -0,0 +1,21 @@ + +**🤖 Copilot Assignment Failed**: The workflow created an issue but could not assign the Copilot coding agent to it. This typically happens when: + +- The `GH_AW_AGENT_TOKEN` secret is missing or has expired +- The token does not have the `issues: write` permission +- The Copilot coding agent is not available for this repository +- GitHub API credentials are invalid (`Bad credentials`) + +**Failed assignments:** +{issues} + +To resolve this, verify that: +1. The `GH_AW_AGENT_TOKEN` secret is configured in your repository settings +2. The token belongs to an account with an active Copilot subscription +3. The token has `issues: write` permission for this repository + +```bash +gh aw secrets set GH_AW_AGENT_TOKEN --value "YOUR_TOKEN" +``` + +See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/auth.mdx diff --git a/pkg/workflow/compile_outputs_issue_test.go b/pkg/workflow/compile_outputs_issue_test.go index 80b86f75a46..0cb33a19501 100644 --- a/pkg/workflow/compile_outputs_issue_test.go +++ b/pkg/workflow/compile_outputs_issue_test.go @@ -339,10 +339,19 @@ This workflow tests that copilot assignment is wired in consolidated safe output if !strings.Contains(lockContent, "name: Assign Copilot to created issues") { t.Error("Expected copilot assignment step in consolidated safe_outputs job") } + if !strings.Contains(lockContent, "id: assign_copilot_to_created_issues") { + t.Error("Expected copilot assignment step to have id: assign_copilot_to_created_issues") + } + if !strings.Contains(lockContent, "continue-on-error: true") { + t.Error("Expected copilot assignment step to have continue-on-error: true so failures propagate as outputs") + } if !strings.Contains(lockContent, "GH_AW_ISSUES_TO_ASSIGN_COPILOT") || !strings.Contains(lockContent, "steps.process_safe_outputs.outputs.issues_to_assign_copilot") { t.Error("Expected assignment step to consume issues_to_assign_copilot from process_safe_outputs") } if !strings.Contains(lockContent, "assign_copilot_to_created_issues.cjs") { t.Error("Expected assignment step to require assign_copilot_to_created_issues.cjs") } + if !strings.Contains(lockContent, "assign_copilot_failure_count") || !strings.Contains(lockContent, "assign_copilot_errors") { + t.Error("Expected safe_outputs job to export assign_copilot_failure_count and assign_copilot_errors outputs for failure propagation") + } } diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 500c4e84c85..ddbfa982d41 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -189,7 +189,9 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa if data.SafeOutputs.CreateIssues != nil && hasCopilotAssignee(data.SafeOutputs.CreateIssues.Assignees) { consolidatedSafeOutputsJobLog.Print("Adding copilot assignment step for created issues") steps = append(steps, " - name: Assign Copilot to created issues\n") + steps = append(steps, " id: assign_copilot_to_created_issues\n") steps = append(steps, " if: steps.process_safe_outputs.outputs.issues_to_assign_copilot != ''\n") + steps = append(steps, " continue-on-error: true\n") steps = append(steps, fmt.Sprintf(" uses: %s\n", GetActionPin("actions/github-script"))) steps = append(steps, " env:\n") steps = append(steps, " GH_AW_ISSUES_TO_ASSIGN_COPILOT: ${{ steps.process_safe_outputs.outputs.issues_to_assign_copilot }}\n") @@ -197,6 +199,9 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa c.addSafeOutputAgentGitHubTokenForConfig(&steps, data, data.SafeOutputs.CreateIssues.GitHubToken) steps = append(steps, " script: |\n") steps = append(steps, generateGitHubScriptWithRequire("assign_copilot_to_created_issues.cjs")) + + outputs["assign_copilot_failure_count"] = "${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_failure_count }}" + outputs["assign_copilot_errors"] = "${{ steps.assign_copilot_to_created_issues.outputs.assign_copilot_errors }}" } } diff --git a/pkg/workflow/notify_comment.go b/pkg/workflow/notify_comment.go index 14440fc604a..c7294a2cfa3 100644 --- a/pkg/workflow/notify_comment.go +++ b/pkg/workflow/notify_comment.go @@ -163,6 +163,12 @@ func (c *Compiler) buildConclusionJob(data *WorkflowData, mainJobName string, sa agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }}\n") } + // Pass copilot assignment failure outputs from safe_outputs job if create-issue with copilot assignee is configured + if data.SafeOutputs != nil && data.SafeOutputs.CreateIssues != nil && hasCopilotAssignee(data.SafeOutputs.CreateIssues.Assignees) { + agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_ASSIGN_COPILOT_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.assign_copilot_failure_count }}\n") + agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_ASSIGN_COPILOT_ERRORS: ${{ needs.safe_outputs.outputs.assign_copilot_errors }}\n") + } + // Pass create_discussion error outputs from safe_outputs job if create-discussions is configured if data.SafeOutputs != nil && data.SafeOutputs.CreateDiscussions != nil { agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_CREATE_DISCUSSION_ERRORS: ${{ needs.safe_outputs.outputs.create_discussion_errors }}\n")