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
4 changes: 4 additions & 0 deletions .github/workflows/agentic-campaign-generator.lock.yml

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

4 changes: 4 additions & 0 deletions .github/workflows/issue-monster.lock.yml

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

4 changes: 4 additions & 0 deletions .github/workflows/workflow-generator.lock.yml

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

12 changes: 12 additions & 0 deletions actions/setup/js/assign_to_agent.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)`);
Expand Down
50 changes: 47 additions & 3 deletions actions/setup/js/handle_agent_failure.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
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}
{secret_verification_context}{assignment_errors_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}
{secret_verification_context}{assignment_errors_context}

### Action Required

Expand Down
18 changes: 9 additions & 9 deletions pkg/workflow/compiler_safe_outputs_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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")
}
}

Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/compiler_safe_outputs_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/notify_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down