From 5567da1c379a18a174ac1cb2d735a070b910edb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 06:44:08 +0000 Subject: [PATCH 1/3] Initial plan From 7d548291e720135b6a251a6036b41bda5281d84b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 07:06:38 +0000 Subject: [PATCH 2/3] Add assignment error reporting to agent failure handling Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/issue-monster.lock.yml | 4 ++ actions/setup/js/assign_to_agent.cjs | 12 +++++ actions/setup/js/handle_agent_failure.cjs | 50 ++++++++++++++++++-- actions/setup/md/agent_failure_comment.md | 2 +- actions/setup/md/agent_failure_issue.md | 2 +- pkg/workflow/compiler_safe_outputs_config.go | 18 +++---- pkg/workflow/compiler_safe_outputs_job.go | 2 + pkg/workflow/notify_comment.go | 6 +++ 8 files changed, 82 insertions(+), 14 deletions(-) diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index bf6b4f62fff..3015d497624 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1047,6 +1047,8 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1311,6 +1313,8 @@ jobs: GH_AW_WORKFLOW_NAME: "Issue Monster" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} + assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: diff --git a/actions/setup/js/assign_to_agent.cjs b/actions/setup/js/assign_to_agent.cjs index 678beda2250..e3d79d26e1e 100644 --- a/actions/setup/js/assign_to_agent.cjs +++ b/actions/setup/js/assign_to_agent.cjs @@ -335,6 +335,18 @@ async function main() { .join("\n"); core.setOutput("assigned_agents", assignedAgents); + // Set assignment error output for failed assignments + const assignmentErrors = results + .filter(r => !r.success) + .map(r => { + const number = r.issue_number || r.pull_number; + const prefix = r.issue_number ? "issue" : "pr"; + return `${prefix}:${number}:${r.agent}:${r.error}`; + }) + .join("\n"); + core.setOutput("assignment_errors", assignmentErrors); + core.setOutput("assignment_error_count", failureCount.toString()); + // Fail if any assignments failed if (failureCount > 0) { core.setFailed(`Failed to assign ${failureCount} agent(s)`); diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 99fa7a3222b..61f9c000a92 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -249,14 +249,20 @@ async function main() { const workflowSource = process.env.GH_AW_WORKFLOW_SOURCE || ""; const workflowSourceURL = process.env.GH_AW_WORKFLOW_SOURCE_URL || ""; 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"; core.info(`Agent conclusion: ${agentConclusion}`); core.info(`Workflow name: ${workflowName}`); core.info(`Secret verification result: ${secretVerificationResult}`); + core.info(`Assignment error count: ${assignmentErrorCount}`); - // Only proceed if the agent job actually failed - if (agentConclusion !== "failure") { - core.info(`Agent job did not fail (conclusion: ${agentConclusion}), skipping failure handling`); + // Check if there are assignment errors (regardless of agent job status) + const hasAssignmentErrors = parseInt(assignmentErrorCount, 10) > 0; + + // Only proceed if the agent job actually failed OR there are assignment errors + if (agentConclusion !== "failure" && !hasAssignmentErrors) { + core.info(`Agent job did not fail and no assignment errors (conclusion: ${agentConclusion}), skipping failure handling`); return; } @@ -305,6 +311,24 @@ async function main() { runId = runIdMatch[1]; } + // Build assignment errors context + let assignmentErrorsContext = ""; + if (hasAssignmentErrors && assignmentErrors) { + assignmentErrorsContext = "\n**⚠️ Agent Assignment Failed**: Failed to assign agent to issues due to insufficient permissions or missing token.\n\n**Assignment Errors:**\n"; + const errorLines = assignmentErrors.split("\n").filter(line => line.trim()); + for (const errorLine of errorLines) { + const parts = errorLine.split(":"); + if (parts.length >= 4) { + const type = parts[0]; // "issue" or "pr" + const number = parts[1]; + const agent = parts[2]; + const error = parts.slice(3).join(":"); // Rest is the error message + assignmentErrorsContext += `- ${type === "issue" ? "Issue" : "PR"} #${number} (agent: ${agent}): ${error}\n`; + } + } + assignmentErrorsContext += "\n"; + } + // Create template context const templateContext = { run_url: runUrl, @@ -315,6 +339,7 @@ async function main() { secret_verification_failed: String(secretVerificationResult === "failed"), secret_verification_context: secretVerificationResult === "failed" ? "\n**⚠️ Secret Verification Failed**: The workflow's secret validation step failed. Please check that the required secrets are configured in your repository settings.\n" : "", + assignment_errors_context: assignmentErrorsContext, }; // Render the comment template @@ -351,6 +376,24 @@ async function main() { // Get current branch information const currentBranch = getCurrentBranch(); + // Build assignment errors context + let assignmentErrorsContext = ""; + if (hasAssignmentErrors && assignmentErrors) { + assignmentErrorsContext = "\n**⚠️ Agent Assignment Failed**: Failed to assign agent to issues due to insufficient permissions or missing token.\n\n**Assignment Errors:**\n"; + const errorLines = assignmentErrors.split("\n").filter(line => line.trim()); + for (const errorLine of errorLines) { + const parts = errorLine.split(":"); + if (parts.length >= 4) { + const type = parts[0]; // "issue" or "pr" + const number = parts[1]; + const agent = parts[2]; + const error = parts.slice(3).join(":"); // Rest is the error message + assignmentErrorsContext += `- ${type === "issue" ? "Issue" : "PR"} #${number} (agent: ${agent}): ${error}\n`; + } + } + assignmentErrorsContext += "\n"; + } + // Create template context with sanitized workflow name const templateContext = { workflow_name: sanitizedWorkflowName, @@ -361,6 +404,7 @@ async function main() { secret_verification_failed: String(secretVerificationResult === "failed"), secret_verification_context: secretVerificationResult === "failed" ? "\n**⚠️ Secret Verification Failed**: The workflow's secret validation step failed. Please check that the required secrets are configured in your repository settings.\n" : "", + assignment_errors_context: assignmentErrorsContext, }; // Render the issue template diff --git a/actions/setup/md/agent_failure_comment.md b/actions/setup/md/agent_failure_comment.md index 05bb1c19808..baeebdcc4a2 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} +{secret_verification_context}{assignment_errors_context} diff --git a/actions/setup/md/agent_failure_issue.md b/actions/setup/md/agent_failure_issue.md index 2210667de78..935e2cd2c57 100644 --- a/actions/setup/md/agent_failure_issue.md +++ b/actions/setup/md/agent_failure_issue.md @@ -4,7 +4,7 @@ **Branch:** {branch} **Run URL:** {run_url}{pull_request_info} -{secret_verification_context} +{secret_verification_context}{assignment_errors_context} ### Action Required diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index abf6cdaee56..c9db00636d9 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -7,21 +7,21 @@ import ( "github.com/githubnext/gh-aw/pkg/logger" ) -var compilerSafeOutputsLog = logger.New("workflow:compiler_safe_outputs_config") +var compilerSafeOutputsConfigLog = logger.New("workflow:compiler_safe_outputs_config") func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *WorkflowData) { if data.SafeOutputs == nil { - compilerSafeOutputsLog.Print("No safe-outputs configuration, skipping handler manager config") + compilerSafeOutputsConfigLog.Print("No safe-outputs configuration, skipping handler manager config") return } - compilerSafeOutputsLog.Print("Building handler manager configuration for safe-outputs") + compilerSafeOutputsConfigLog.Print("Building handler manager configuration for safe-outputs") config := make(map[string]map[string]any) // Add config for each enabled safe output type with their options // Presence in config = enabled, so no need for "enabled": true field if data.SafeOutputs.CreateIssues != nil { - compilerSafeOutputsLog.Print("Adding create_issue handler configuration") + compilerSafeOutputsConfigLog.Print("Adding create_issue handler configuration") cfg := data.SafeOutputs.CreateIssues handlerConfig := make(map[string]any) if cfg.Max > 0 { @@ -526,7 +526,7 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow // Only add the env var if there are handlers to configure if len(config) > 0 { - compilerSafeOutputsLog.Printf("Marshaling handler config with %d handlers", len(config)) + compilerSafeOutputsConfigLog.Printf("Marshaling handler config with %d handlers", len(config)) configJSON, err := json.Marshal(config) if err != nil { consolidatedSafeOutputsLog.Printf("Failed to marshal handler config: %v", err) @@ -535,9 +535,9 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow // Escape the JSON for YAML (handle quotes and special chars) configStr := string(configJSON) *steps = append(*steps, fmt.Sprintf(" GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: %q\n", configStr)) - compilerSafeOutputsLog.Printf("Added handler config env var: size=%d bytes", len(configStr)) + compilerSafeOutputsConfigLog.Printf("Added handler config env var: size=%d bytes", len(configStr)) } else { - compilerSafeOutputsLog.Print("No handlers configured, skipping config env var") + compilerSafeOutputsConfigLog.Print("No handlers configured, skipping config env var") } } @@ -546,11 +546,11 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow // These handlers require GH_AW_PROJECT_GITHUB_TOKEN and are processed separately from the main handler manager. func (c *Compiler) addProjectHandlerManagerConfigEnvVar(steps *[]string, data *WorkflowData) { if data.SafeOutputs == nil { - compilerSafeOutputsLog.Print("No safe-outputs configuration, skipping project handler config") + compilerSafeOutputsConfigLog.Print("No safe-outputs configuration, skipping project handler config") return } - compilerSafeOutputsLog.Print("Building project handler manager configuration") + compilerSafeOutputsConfigLog.Print("Building project handler manager configuration") config := make(map[string]map[string]any) // Add config for project-related safe output types diff --git a/pkg/workflow/compiler_safe_outputs_job.go b/pkg/workflow/compiler_safe_outputs_job.go index 3b70176d995..2671b385e3b 100644 --- a/pkg/workflow/compiler_safe_outputs_job.go +++ b/pkg/workflow/compiler_safe_outputs_job.go @@ -261,6 +261,8 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa safeOutputStepNames = append(safeOutputStepNames, stepConfig.StepID) outputs["assign_to_agent_assigned"] = "${{ steps.assign_to_agent.outputs.assigned }}" + outputs["assign_to_agent_assignment_errors"] = "${{ steps.assign_to_agent.outputs.assignment_errors }}" + outputs["assign_to_agent_assignment_error_count"] = "${{ steps.assign_to_agent.outputs.assignment_error_count }}" permissions.Merge(NewPermissionsContentsReadIssuesWrite()) } diff --git a/pkg/workflow/notify_comment.go b/pkg/workflow/notify_comment.go index 6547ba5294e..52b07ed2cb6 100644 --- a/pkg/workflow/notify_comment.go +++ b/pkg/workflow/notify_comment.go @@ -152,6 +152,12 @@ func (c *Compiler) buildConclusionJob(data *WorkflowData, mainJobName string, sa agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_AGENT_CONCLUSION: ${{ needs.%s.result }}\n", mainJobName)) agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.%s.outputs.secret_verification_result }}\n", mainJobName)) + // Pass assignment error outputs from safe_outputs job if assign-to-agent is configured + if data.SafeOutputs != nil && data.SafeOutputs.AssignToAgent != nil { + agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }}\n") + agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }}\n") + } + // Pass custom messages config if present if data.SafeOutputs != nil && data.SafeOutputs.Messages != nil { messagesJSON, err := serializeMessagesConfig(data.SafeOutputs.Messages) From 1cca0db5b12359e3e33ccdda61aee3813ec3e74b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 07:11:55 +0000 Subject: [PATCH 3/3] Final validation: code formatting and recompilation complete Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-campaign-generator.lock.yml | 4 ++++ .github/workflows/workflow-generator.lock.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/agentic-campaign-generator.lock.yml b/.github/workflows/agentic-campaign-generator.lock.yml index 80a08bb09d4..054f510cada 100644 --- a/.github/workflows/agentic-campaign-generator.lock.yml +++ b/.github/workflows/agentic-campaign-generator.lock.yml @@ -1200,6 +1200,8 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"runStarted\":\"### :rocket: Campaign setup started\\nCreating a tracking Project and generating campaign files + orchestrator workflow.\\nNo action needed — the [{workflow_name}]({run_url}) will open a pull request and post the link + checklist back on this issue when ready.\\n\\u003e To stop this run: remove the label that started it.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runSuccess\":\"### :white_check_mark: Campaign setup complete\\nTracking Project created and pull request with generated campaign files is ready.\\n**Next steps**: Review + merge the PR, then run the campaign from the Actions tab.\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\",\"runFailure\":\"### :x: Campaign setup {status}\\n**Common causes**:\\n- `GH_AW_PROJECT_GITHUB_TOKEN` is missing or invalid\\n- Token lacks access to GitHub Projects\\n**Action required**:\\n- Fix the first error in the logs\\n- Re-apply the label to re-run\\n\\u003e **Troubleshooting**: https://githubnext.github.io/gh-aw/guides/campaigns/flow/#when-something-goes-wrong\\n\\u003e **Docs**: https://githubnext.github.io/gh-aw/guides/campaigns/getting-started/\"}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1465,6 +1467,8 @@ jobs: GH_AW_WORKFLOW_NAME: "Agentic Campaign Generator" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} + assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }} process_project_safe_outputs_processed_count: ${{ steps.process_project_safe_outputs.outputs.processed_count }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} diff --git a/.github/workflows/workflow-generator.lock.yml b/.github/workflows/workflow-generator.lock.yml index a9be8fd502c..9bbf10eaa4b 100644 --- a/.github/workflows/workflow-generator.lock.yml +++ b/.github/workflows/workflow-generator.lock.yml @@ -1006,6 +1006,8 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1264,6 +1266,8 @@ jobs: GH_AW_WORKFLOW_NAME: "Workflow Generator" outputs: assign_to_agent_assigned: ${{ steps.assign_to_agent.outputs.assigned }} + assign_to_agent_assignment_error_count: ${{ steps.assign_to_agent.outputs.assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.assign_to_agent.outputs.assignment_errors }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: