diff --git a/actions/setup/js/handle_agent_failure.cjs b/actions/setup/js/handle_agent_failure.cjs index 67879d3134f..b27058eee28 100644 --- a/actions/setup/js/handle_agent_failure.cjs +++ b/actions/setup/js/handle_agent_failure.cjs @@ -77,10 +77,14 @@ async function findPullRequestForCurrentBranch() { /** * Search for or create the parent issue for all agentic workflow failures * @param {number|null} previousParentNumber - Previous parent issue number if creating due to limit + * @param {string} [ownerOverride] - Repository owner override (from failure-issue-repo config) + * @param {string} [repoOverride] - Repository name override (from failure-issue-repo config) * @returns {Promise<{number: number, node_id: string}>} Parent issue number and node ID */ -async function ensureParentIssue(previousParentNumber = null) { - const { owner, repo } = context.repo; +async function ensureParentIssue(previousParentNumber = null, ownerOverride, repoOverride) { + const { owner: contextOwner, repo: contextRepo } = context.repo; + const owner = ownerOverride || contextOwner; + const repo = repoOverride || contextRepo; const parentTitle = "[aw] Failed runs"; const parentLabel = "agentic-workflows"; @@ -696,7 +700,18 @@ async function main() { return; } - const { owner, repo } = context.repo; + // Determine the target repository for failure issues + // If GH_AW_FAILURE_ISSUE_REPO is set, use that repo instead of the current repo + const failureIssueRepo = process.env.GH_AW_FAILURE_ISSUE_REPO || ""; + let owner, repo; + if (failureIssueRepo && failureIssueRepo.includes("/")) { + const parts = failureIssueRepo.split("/"); + owner = parts[0]; + repo = parts[1]; + core.info(`Using configured failure issue repo: ${owner}/${repo}`); + } else { + ({ owner, repo } = context.repo); + } // Try to find a pull request for the current branch const pullRequest = await findPullRequestForCurrentBranch(); @@ -708,7 +723,7 @@ async function main() { let parentIssue; if (groupReports) { try { - parentIssue = await ensureParentIssue(); + parentIssue = await ensureParentIssue(null, owner, repo); } catch (error) { core.warning(`Could not create parent issue, proceeding without parent: ${getErrorMessage(error)}`); // Continue without parent issue diff --git a/docs/src/content/docs/reference/glossary.md b/docs/src/content/docs/reference/glossary.md index 946424825bb..bd45af315ff 100644 --- a/docs/src/content/docs/reference/glossary.md +++ b/docs/src/content/docs/reference/glossary.md @@ -139,6 +139,17 @@ safe-outputs: See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/). +### Failure Issue Repository (`failure-issue-repo:`) + +A `safe-outputs` option that redirects failure tracking issues to a different repository. Useful when the workflow's repository has issues disabled: + +```yaml +safe-outputs: + failure-issue-repo: github/docs-engineering +``` + +See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/). + ### Upload Assets A safe output capability for uploading generated files (screenshots, charts, reports) to an orphaned git branch for persistent storage. The AI calls the `upload_asset` tool to register files, which are committed to a dedicated assets branch by a separate permission-controlled job. Assets are accessible via GitHub raw URLs. Commonly used for visual testing artifacts, data visualizations, and generated documentation. diff --git a/docs/src/content/docs/reference/safe-outputs.md b/docs/src/content/docs/reference/safe-outputs.md index 60518e705d1..f6218677b9c 100644 --- a/docs/src/content/docs/reference/safe-outputs.md +++ b/docs/src/content/docs/reference/safe-outputs.md @@ -1232,6 +1232,18 @@ safe-outputs: This mirrors the `noop.report-as-issue` pattern. Use this to silence noisy failure reports for workflows where failures are expected or handled externally. +### Failure Issue Repository (`failure-issue-repo:`) + +Redirects failure tracking issues to a different repository. Useful when the current repository has issues disabled (e.g. `github/docs-internal`). + +```yaml wrap +safe-outputs: + failure-issue-repo: github/docs-engineering + create-issue: +``` + +The value must be in `owner/repo` format. The `GITHUB_TOKEN` used must have permission to create issues in the target repository. When not set, failure issues are created in the current repository. + ### Group Reports (`group-reports:`) Controls whether failed workflow runs are grouped under a parent "[aw] Failed runs" issue. This is opt-in and defaults to `false`. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 19b85767c11..139fb79f757 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -7198,6 +7198,12 @@ "default": true, "examples": [false, true] }, + "failure-issue-repo": { + "type": "string", + "description": "Repository to create failure tracking issues in, in the format 'owner/repo'. Useful when the current repository has issues disabled. Defaults to the current repository.", + "pattern": "^[^/]+/[^/]+$", + "examples": ["github/docs-engineering", "myorg/infra-alerts"] + }, "max-bot-mentions": { "description": "Maximum number of bot trigger references (e.g. 'fixes #123', 'closes #456') allowed in output before all of them are neutralized. Default: 10. Supports integer or GitHub Actions expression (e.g. '${{ inputs.max-bot-mentions }}').", "oneOf": [ diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 2b133574a4b..d56dee845ca 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -498,6 +498,7 @@ type SafeOutputsConfig struct { Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included) GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false) ReportFailureAsIssue *bool `yaml:"report-failure-as-issue,omitempty"` // If false, disables creating failure tracking issues when workflows fail (default: true) + FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo MaxBotMentions *string `yaml:"max-bot-mentions,omitempty"` // Maximum bot trigger references (e.g. 'fixes #123') allowed before filtering. Default: 10. Supports integer or GitHub Actions expression. Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection diff --git a/pkg/workflow/imports.go b/pkg/workflow/imports.go index b44fb77ab3c..e95245a7923 100644 --- a/pkg/workflow/imports.go +++ b/pkg/workflow/imports.go @@ -687,6 +687,9 @@ func mergeSafeOutputConfig(result *SafeOutputsConfig, config map[string]any, c * if !result.GroupReports && importedConfig.GroupReports { result.GroupReports = true } + if result.FailureIssueRepo == "" && importedConfig.FailureIssueRepo != "" { + result.FailureIssueRepo = importedConfig.FailureIssueRepo + } if result.MaxBotMentions == nil && importedConfig.MaxBotMentions != nil { result.MaxBotMentions = importedConfig.MaxBotMentions } diff --git a/pkg/workflow/notify_comment.go b/pkg/workflow/notify_comment.go index a477439f380..40492f176ba 100644 --- a/pkg/workflow/notify_comment.go +++ b/pkg/workflow/notify_comment.go @@ -205,6 +205,11 @@ func (c *Compiler) buildConclusionJob(data *WorkflowData, mainJobName string, sa agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_FAILURE_REPORT_AS_ISSUE: \"true\"\n") } + // Pass failure-issue-repo configuration (optional, defaults to current repo) + if data.SafeOutputs != nil && data.SafeOutputs.FailureIssueRepo != "" { + agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_FAILURE_ISSUE_REPO: %q\n", data.SafeOutputs.FailureIssueRepo)) + } + // Pass timeout minutes value so the failure handler can provide an actionable hint when timed out timeoutValue := strings.TrimPrefix(data.TimeoutMinutes, "timeout-minutes: ") if timeoutValue != "" { diff --git a/pkg/workflow/safe_outputs_config.go b/pkg/workflow/safe_outputs_config.go index 9a1d6b8deba..c3c18d1aea4 100644 --- a/pkg/workflow/safe_outputs_config.go +++ b/pkg/workflow/safe_outputs_config.go @@ -469,6 +469,14 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } + // Handle failure-issue-repo (repository for failure issues, format: "owner/repo") + if failureIssueRepo, exists := outputMap["failure-issue-repo"]; exists { + if failureIssueRepoStr, ok := failureIssueRepo.(string); ok && failureIssueRepoStr != "" { + config.FailureIssueRepo = failureIssueRepoStr + safeOutputsConfigLog.Printf("Failure issue repo: %s", failureIssueRepoStr) + } + } + // Handle max-bot-mentions (templatable integer) if err := preprocessIntFieldAsString(outputMap, "max-bot-mentions", safeOutputsConfigLog); err != nil { safeOutputsConfigLog.Printf("max-bot-mentions: %v", err)