Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/breaking-change-checker.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .github/workflows/daily-doc-healer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .github/workflows/duplicate-code-detector.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions actions/setup/js/assign_copilot_to_created_issues.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assign_copilot_errors currently encodes failures as issue:<number>:copilot:<error> (no repository), but issues_to_assign_copilot supports cross-repo entries (owner/repo:number). If multiple repos are involved, the failure context rendered in handle_agent_failure becomes ambiguous (issue numbers aren’t globally unique) and can mislead remediation. Consider including the repo slug in the output format (and updating the parser/formatter in handle_agent_failure.cjs and the markdown template accordingly).

Suggested change
const assignCopilotErrors = failedResults.map(r => `issue:${r.issue_number}:copilot:${r.error}`).join("\n");
// Include the repository slug so failures are unambiguous across multiple repositories.
const assignCopilotErrors = failedResults.map(r => `issue:${r.repo}#${r.issue_number}:copilot:${r.error}`).join("\n");

Copilot uses AI. Check for mistakes.
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`);
}
}

Expand Down
48 changes: 46 additions & 2 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 || "";
Expand Down Expand Up @@ -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}`);
Expand All @@ -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;

Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -1161,4 +1205,4 @@ async function main() {
}
}

module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext, buildTimeoutContext };
module.exports = { main, buildCodePushFailureContext, buildPushRepoMemoryFailureContext, buildAppTokenMintingFailedContext, buildLockdownCheckFailedContext, buildTimeoutContext, buildAssignCopilotFailureContext };
2 changes: 1 addition & 1 deletion actions/setup/md/agent_failure_comment.md
Original file line number Diff line number Diff line change
@@ -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}
2 changes: 1 addition & 1 deletion actions/setup/md/agent_failure_issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 21 additions & 0 deletions actions/setup/md/assign_copilot_to_created_issues_failure.md
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions pkg/workflow/compile_outputs_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
5 changes: 5 additions & 0 deletions pkg/workflow/compiler_safe_outputs_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,19 @@ 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")
steps = append(steps, " with:\n")
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 }}"
}
}

Expand Down
Loading
Loading