Skip to content
Closed
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
52 changes: 49 additions & 3 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,21 @@ 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 safeOutputPermissionErrors = process.env.GH_AW_SAFE_OUTPUT_PERMISSION_ERRORS || "";
const safeOutputPermissionErrorCount = process.env.GH_AW_SAFE_OUTPUT_PERMISSION_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}`);
core.info(`Safe output permission error count: ${safeOutputPermissionErrorCount}`);

// Check if there are assignment errors (regardless of agent job status)
const hasAssignmentErrors = parseInt(assignmentErrorCount, 10) > 0;

// Check if there are safe output permission errors
const hasSafeOutputPermissionErrors = parseInt(safeOutputPermissionErrorCount, 10) > 0;

// Check if agent succeeded but produced no safe outputs
let hasMissingSafeOutputs = false;
if (agentConclusion === "success") {
Expand All @@ -273,9 +279,9 @@ async function main() {
}
}

// Only proceed if the agent job actually failed OR there are assignment errors OR missing safe outputs
if (agentConclusion !== "failure" && !hasAssignmentErrors && !hasMissingSafeOutputs) {
core.info(`Agent job did not fail and no assignment errors and has safe outputs (conclusion: ${agentConclusion}), skipping failure handling`);
// Only proceed if the agent job actually failed OR there are assignment errors OR missing safe outputs OR safe output permission errors
if (agentConclusion !== "failure" && !hasAssignmentErrors && !hasMissingSafeOutputs && !hasSafeOutputPermissionErrors) {
core.info(`Agent job did not fail and no assignment errors and has safe outputs and no permission errors (conclusion: ${agentConclusion}), skipping failure handling`);
return;
}

Expand Down Expand Up @@ -342,6 +348,25 @@ async function main() {
assignmentErrorsContext += "\n";
}

// Build safe output permission errors context
let safeOutputPermissionErrorsContext = "";
if (hasSafeOutputPermissionErrors && safeOutputPermissionErrors) {
safeOutputPermissionErrorsContext = "\n**⚠️ Safe Output Permission Errors**: Failed to perform safe output operations due to insufficient permissions.\n\n**Permission Errors:**\n";
const errorLines = safeOutputPermissionErrors.split("\n").filter(line => line.trim());
for (const errorLine of errorLines) {
const parts = errorLine.split(":");
if (parts.length >= 5) {
const handler = parts[0]; // e.g., "update_project"
const type = parts[1]; // "issue" or "pr"
const number = parts[2];
const operation = parts[3]; // e.g., "campaign_label"
const error = parts.slice(4).join(":"); // Rest is the error message
safeOutputPermissionErrorsContext += `- ${type === "issue" ? "Issue" : "PR"} #${number} (${handler} - ${operation}): ${error}\n`;
}
}
safeOutputPermissionErrorsContext += "\n";
}

// Build missing safe outputs context
let missingSafeOutputsContext = "";
if (hasMissingSafeOutputs) {
Expand All @@ -362,6 +387,7 @@ async function main() {
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,
safe_output_permission_errors_context: safeOutputPermissionErrorsContext,
missing_safe_outputs_context: missingSafeOutputsContext,
};

Expand Down Expand Up @@ -417,6 +443,25 @@ async function main() {
assignmentErrorsContext += "\n";
}

// Build safe output permission errors context
let safeOutputPermissionErrorsContext = "";
if (hasSafeOutputPermissionErrors && safeOutputPermissionErrors) {
safeOutputPermissionErrorsContext = "\n**⚠️ Safe Output Permission Errors**: Failed to perform safe output operations due to insufficient permissions.\n\n**Permission Errors:**\n";
const errorLines = safeOutputPermissionErrors.split("\n").filter(line => line.trim());
for (const errorLine of errorLines) {
const parts = errorLine.split(":");
if (parts.length >= 5) {
const handler = parts[0]; // e.g., "update_project"
const type = parts[1]; // "issue" or "pr"
const number = parts[2];
const operation = parts[3]; // e.g., "campaign_label"
const error = parts.slice(4).join(":"); // Rest is the error message
safeOutputPermissionErrorsContext += `- ${type === "issue" ? "Issue" : "PR"} #${number} (${handler} - ${operation}): ${error}\n`;
}
}
safeOutputPermissionErrorsContext += "\n";
}

// Build missing safe outputs context
let missingSafeOutputsContext = "";
if (hasMissingSafeOutputs) {
Expand All @@ -437,6 +482,7 @@ async function main() {
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,
safe_output_permission_errors_context: safeOutputPermissionErrorsContext,
missing_safe_outputs_context: missingSafeOutputsContext,
};

Expand Down
18 changes: 17 additions & 1 deletion actions/setup/js/update_project.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,23 @@ async function updateProject(output) {
try {
await github.rest.issues.addLabels({ owner, repo, issue_number: contentNumber, labels: [formatCampaignLabel(campaignId)] });
} catch (labelError) {
core.warning(`Failed to add campaign label: ${getErrorMessage(labelError)}`);
const errorMessage = getErrorMessage(labelError);
// Check if this is a permission error
if (errorMessage.includes("Resource not accessible by personal access token") || errorMessage.includes("Resource not accessible by integration") || errorMessage.includes("Insufficient permissions")) {
// Track permission error for conclusion job to report
const existingErrors = process.env.GH_AW_SAFE_OUTPUT_PERMISSION_ERRORS || "";
const errorEntry = `update_project:issue:${contentNumber}:campaign_label:${errorMessage}`;
const newErrors = existingErrors ? `${existingErrors}\n${errorEntry}` : errorEntry;
core.exportVariable("GH_AW_SAFE_OUTPUT_PERMISSION_ERRORS", newErrors);

const errorCount = newErrors.split("\n").length;
core.exportVariable("GH_AW_SAFE_OUTPUT_PERMISSION_ERROR_COUNT", errorCount.toString());

core.info(`Permission error adding campaign label tracked for conclusion job reporting`);
} else {
// Non-permission errors should still be warned about
core.warning(`Failed to add campaign label: ${errorMessage}`);
}
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion actions/setup/js/update_project.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ describe("updateProject", () => {
expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining('Failed to create field "NonExistentField"'));
});

it("warns when adding the campaign label fails", async () => {
it("warns when adding the campaign label fails with non-permission error", async () => {
const projectUrl = "https://github.com/orgs/testowner/projects/60";
const output = { type: "update_project", project: projectUrl, content_type: "issue", content_number: 50, campaign_id: "test-campaign" };

Expand All @@ -680,6 +680,22 @@ describe("updateProject", () => {
expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Failed to add campaign label"));
});

it("tracks permission errors for conclusion job when adding campaign label", async () => {
const projectUrl = "https://github.com/orgs/testowner/projects/60";
const output = { type: "update_project", project: projectUrl, content_type: "issue", content_number: 50, campaign_id: "test-campaign" };

queueResponses([repoResponse(), viewerResponse(), orgProjectV2Response(projectUrl, 60, "project-label"), issueResponse("issue-id-50"), emptyItemsResponse(), { addProjectV2ItemById: { item: { id: "item-label" } } }]);

mockGithub.rest.issues.addLabels.mockRejectedValueOnce(new Error("Resource not accessible by personal access token"));

await updateProject(output);

// Permission errors should be tracked via exportVariable for conclusion job
expect(mockCore.exportVariable).toHaveBeenCalledWith("GH_AW_SAFE_OUTPUT_PERMISSION_ERRORS", expect.stringContaining("update_project:issue:50:campaign_label"));
expect(mockCore.exportVariable).toHaveBeenCalledWith("GH_AW_SAFE_OUTPUT_PERMISSION_ERROR_COUNT", "1");
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Permission error adding campaign label tracked for conclusion job reporting"));
});

it("rejects non-URL project identifier", async () => {
const output = { type: "update_project", project: "My Campaign", campaign_id: "my-campaign-123" };
await expect(updateProject(output)).rejects.toThrow(/full GitHub project URL/);
Expand Down
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}{assignment_errors_context}{missing_safe_outputs_context}
{secret_verification_context}{assignment_errors_context}{safe_output_permission_errors_context}{missing_safe_outputs_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 URL:** {run_url}{pull_request_info}

{secret_verification_context}{assignment_errors_context}{missing_safe_outputs_context}
{secret_verification_context}{assignment_errors_context}{safe_output_permission_errors_context}{missing_safe_outputs_context}

### Action Required

Expand Down
Loading