Skip to content

Commit 5af0fee

Browse files
authored
feat: add safe-outputs.failure-issue-repo to redirect failure issues to a different repo (#20429)
1 parent e3a377b commit 5af0fee

File tree

8 files changed

+65
-4
lines changed

8 files changed

+65
-4
lines changed

actions/setup/js/handle_agent_failure.cjs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ async function findPullRequestForCurrentBranch() {
7777
/**
7878
* Search for or create the parent issue for all agentic workflow failures
7979
* @param {number|null} previousParentNumber - Previous parent issue number if creating due to limit
80+
* @param {string} [ownerOverride] - Repository owner override (from failure-issue-repo config)
81+
* @param {string} [repoOverride] - Repository name override (from failure-issue-repo config)
8082
* @returns {Promise<{number: number, node_id: string}>} Parent issue number and node ID
8183
*/
82-
async function ensureParentIssue(previousParentNumber = null) {
83-
const { owner, repo } = context.repo;
84+
async function ensureParentIssue(previousParentNumber = null, ownerOverride, repoOverride) {
85+
const { owner: contextOwner, repo: contextRepo } = context.repo;
86+
const owner = ownerOverride || contextOwner;
87+
const repo = repoOverride || contextRepo;
8488
const parentTitle = "[aw] Failed runs";
8589
const parentLabel = "agentic-workflows";
8690

@@ -696,7 +700,18 @@ async function main() {
696700
return;
697701
}
698702

699-
const { owner, repo } = context.repo;
703+
// Determine the target repository for failure issues
704+
// If GH_AW_FAILURE_ISSUE_REPO is set, use that repo instead of the current repo
705+
const failureIssueRepo = process.env.GH_AW_FAILURE_ISSUE_REPO || "";
706+
let owner, repo;
707+
if (failureIssueRepo && failureIssueRepo.includes("/")) {
708+
const parts = failureIssueRepo.split("/");
709+
owner = parts[0];
710+
repo = parts[1];
711+
core.info(`Using configured failure issue repo: ${owner}/${repo}`);
712+
} else {
713+
({ owner, repo } = context.repo);
714+
}
700715

701716
// Try to find a pull request for the current branch
702717
const pullRequest = await findPullRequestForCurrentBranch();
@@ -708,7 +723,7 @@ async function main() {
708723
let parentIssue;
709724
if (groupReports) {
710725
try {
711-
parentIssue = await ensureParentIssue();
726+
parentIssue = await ensureParentIssue(null, owner, repo);
712727
} catch (error) {
713728
core.warning(`Could not create parent issue, proceeding without parent: ${getErrorMessage(error)}`);
714729
// Continue without parent issue

docs/src/content/docs/reference/glossary.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ safe-outputs:
139139
140140
See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/).
141141
142+
### Failure Issue Repository (`failure-issue-repo:`)
143+
144+
A `safe-outputs` option that redirects failure tracking issues to a different repository. Useful when the workflow's repository has issues disabled:
145+
146+
```yaml
147+
safe-outputs:
148+
failure-issue-repo: github/docs-engineering
149+
```
150+
151+
See [Safe Outputs Reference](/gh-aw/reference/safe-outputs/).
152+
142153
### Upload Assets
143154

144155
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.

docs/src/content/docs/reference/safe-outputs.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,18 @@ safe-outputs:
12321232

12331233
This mirrors the `noop.report-as-issue` pattern. Use this to silence noisy failure reports for workflows where failures are expected or handled externally.
12341234

1235+
### Failure Issue Repository (`failure-issue-repo:`)
1236+
1237+
Redirects failure tracking issues to a different repository. Useful when the current repository has issues disabled (e.g. `github/docs-internal`).
1238+
1239+
```yaml wrap
1240+
safe-outputs:
1241+
failure-issue-repo: github/docs-engineering
1242+
create-issue:
1243+
```
1244+
1245+
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.
1246+
12351247
### Group Reports (`group-reports:`)
12361248

12371249
Controls whether failed workflow runs are grouped under a parent "[aw] Failed runs" issue. This is opt-in and defaults to `false`.

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7198,6 +7198,12 @@
71987198
"default": true,
71997199
"examples": [false, true]
72007200
},
7201+
"failure-issue-repo": {
7202+
"type": "string",
7203+
"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.",
7204+
"pattern": "^[^/]+/[^/]+$",
7205+
"examples": ["github/docs-engineering", "myorg/infra-alerts"]
7206+
},
72017207
"max-bot-mentions": {
72027208
"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 }}').",
72037209
"oneOf": [

pkg/workflow/compiler_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ type SafeOutputsConfig struct {
498498
Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included)
499499
GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false)
500500
ReportFailureAsIssue *bool `yaml:"report-failure-as-issue,omitempty"` // If false, disables creating failure tracking issues when workflows fail (default: true)
501+
FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo
501502
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.
502503
Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code
503504
IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection

pkg/workflow/imports.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,9 @@ func mergeSafeOutputConfig(result *SafeOutputsConfig, config map[string]any, c *
687687
if !result.GroupReports && importedConfig.GroupReports {
688688
result.GroupReports = true
689689
}
690+
if result.FailureIssueRepo == "" && importedConfig.FailureIssueRepo != "" {
691+
result.FailureIssueRepo = importedConfig.FailureIssueRepo
692+
}
690693
if result.MaxBotMentions == nil && importedConfig.MaxBotMentions != nil {
691694
result.MaxBotMentions = importedConfig.MaxBotMentions
692695
}

pkg/workflow/notify_comment.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ func (c *Compiler) buildConclusionJob(data *WorkflowData, mainJobName string, sa
205205
agentFailureEnvVars = append(agentFailureEnvVars, " GH_AW_FAILURE_REPORT_AS_ISSUE: \"true\"\n")
206206
}
207207

208+
// Pass failure-issue-repo configuration (optional, defaults to current repo)
209+
if data.SafeOutputs != nil && data.SafeOutputs.FailureIssueRepo != "" {
210+
agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_FAILURE_ISSUE_REPO: %q\n", data.SafeOutputs.FailureIssueRepo))
211+
}
212+
208213
// Pass timeout minutes value so the failure handler can provide an actionable hint when timed out
209214
timeoutValue := strings.TrimPrefix(data.TimeoutMinutes, "timeout-minutes: ")
210215
if timeoutValue != "" {

pkg/workflow/safe_outputs_config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,14 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut
469469
}
470470
}
471471

472+
// Handle failure-issue-repo (repository for failure issues, format: "owner/repo")
473+
if failureIssueRepo, exists := outputMap["failure-issue-repo"]; exists {
474+
if failureIssueRepoStr, ok := failureIssueRepo.(string); ok && failureIssueRepoStr != "" {
475+
config.FailureIssueRepo = failureIssueRepoStr
476+
safeOutputsConfigLog.Printf("Failure issue repo: %s", failureIssueRepoStr)
477+
}
478+
}
479+
472480
// Handle max-bot-mentions (templatable integer)
473481
if err := preprocessIntFieldAsString(outputMap, "max-bot-mentions", safeOutputsConfigLog); err != nil {
474482
safeOutputsConfigLog.Printf("max-bot-mentions: %v", err)

0 commit comments

Comments
 (0)