diff --git a/.github/workflows/cgo.yml b/.github/workflows/cgo.yml index a139390f00..d6843876f6 100644 --- a/.github/workflows/cgo.yml +++ b/.github/workflows/cgo.yml @@ -1750,9 +1750,34 @@ jobs: } } + // Fetch all jobs for this run to get direct job links + const jobsResponse = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + per_page: 100, + }); + + // Build a map from job name to job URL for failed jobs + const jobUrlMap = {}; + for (const job of jobsResponse.data.jobs) { + if (failedJobs.includes(job.name)) { + jobUrlMap[job.name] = job.html_url; + } + } + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; const expiresAt = new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString(); + // Format expiration line using the gh-aw-expires XML comment format + const expiresDate = new Date(expiresAt); + const humanReadableDate = expiresDate.toLocaleString('en-US', { + dateStyle: 'medium', + timeStyle: 'short', + timeZone: 'UTC', + }); + const expirationLine = `- [x] expires on ${humanReadableDate} UTC`; + const body = [ `## CGO Workflow Failure`, ``, @@ -1766,9 +1791,13 @@ jobs: ``, `## Failed Jobs`, ``, - ...failedJobs.map(name => `- \`${name}\``), + // Map job names to direct links; fall back to plain text if a job ID wasn't found + ...failedJobs.map(name => jobUrlMap[name] + ? `- [\`${name}\`](${jobUrlMap[name]})` + : `- \`${name}\``), ``, `> This issue expires at ${expiresAt}. Please investigate the failed jobs above and close once resolved.`, + `> ${expirationLine}`, ].join('\n'); const issue = await github.rest.issues.create({ diff --git a/docs/src/content/docs/agent-factory-status.mdx b/docs/src/content/docs/agent-factory-status.mdx index a5b319e04c..fd6dc1675a 100644 --- a/docs/src/content/docs/agent-factory-status.mdx +++ b/docs/src/content/docs/agent-factory-status.mdx @@ -99,6 +99,7 @@ These are experimental agentic workflows used by the GitHub Next team to learn, | [Delight](https://github.com/github/gh-aw/blob/main/.github/workflows/delight.md) | copilot | [![Delight](https://github.com/github/gh-aw/actions/workflows/delight.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/delight.lock.yml) | - | - | | [Dependabot Burner](https://github.com/github/gh-aw/blob/main/.github/workflows/dependabot-burner.md) | copilot | [![Dependabot Burner](https://github.com/github/gh-aw/actions/workflows/dependabot-burner.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/dependabot-burner.lock.yml) | - | - | | [Dependabot Dependency Checker](https://github.com/github/gh-aw/blob/main/.github/workflows/dependabot-go-checker.md) | copilot | [![Dependabot Dependency Checker](https://github.com/github/gh-aw/actions/workflows/dependabot-go-checker.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/dependabot-go-checker.lock.yml) | `20 9 * * 1,3,5` | - | +| [Deployment Incident Monitor](https://github.com/github/gh-aw/blob/main/.github/workflows/deployment-incident-monitor.md) | copilot | [![Deployment Incident Monitor](https://github.com/github/gh-aw/actions/workflows/deployment-incident-monitor.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/deployment-incident-monitor.lock.yml) | - | - | | [Design Decision Gate 🏗️](https://github.com/github/gh-aw/blob/main/.github/workflows/design-decision-gate.md) | claude | [![Design Decision Gate 🏗️](https://github.com/github/gh-aw/actions/workflows/design-decision-gate.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/design-decision-gate.lock.yml) | - | - | | [Dev](https://github.com/github/gh-aw/blob/main/.github/workflows/dev.md) | copilot | [![Dev](https://github.com/github/gh-aw/actions/workflows/dev.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/dev.lock.yml) | `daily around 9:00` | - | | [Dev Hawk](https://github.com/github/gh-aw/blob/main/.github/workflows/dev-hawk.md) | copilot | [![Dev Hawk](https://github.com/github/gh-aw/actions/workflows/dev-hawk.lock.yml/badge.svg)](https://github.com/github/gh-aw/actions/workflows/dev-hawk.lock.yml) | - | - | diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index d8020a1184..3d056cd54b 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -482,7 +482,17 @@ on: # Option 2: object deployment_status: - {} + # Filter to specific deployment states (compiled into if condition). Use a string + # for one state or an array for multiple states. + # (optional) + # This field supports multiple formats (oneOf): + + # Option 1: string + state: "error" + + # Option 2: array + state: [] + # Array items: string # Fork event trigger that runs when someone forks the repository # (optional) @@ -3595,7 +3605,7 @@ safe-outputs: github-token-for-extra-empty-commit: "example-value" # Controls protected-file protection. String form: blocked (default), allowed, or - # fallback-to-issue. Object form: { policy, exclude } to customize the + # fallback-to-issue. Object form: { policy, exclude } to customise the # protected-file set. # (optional) # This field supports multiple formats (oneOf): @@ -4777,7 +4787,7 @@ safe-outputs: # Array of strings # Controls protected-file protection. String form: blocked (default), allowed, or - # fallback-to-issue. Object form: { policy, exclude } to customize the + # fallback-to-issue. Object form: { policy, exclude } to customise the # protected-file set. # (optional) # This field supports multiple formats (oneOf): @@ -5298,7 +5308,7 @@ safe-outputs: # Default values injected when the model omits a field # (optional) defaults: - # Behavior when no files match: 'error' (default) or 'ignore' + # Behaviour when no files match: 'error' (default) or 'ignore' # (optional) if-no-files: "error" diff --git a/pkg/gitutil/spec_test.go b/pkg/gitutil/spec_test.go index 84ff4d1cd4..a397821337 100644 --- a/pkg/gitutil/spec_test.go +++ b/pkg/gitutil/spec_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TestSpec_PublicAPI_IsRateLimitError validates the documented behavior of @@ -274,7 +275,7 @@ func TestSpec_PublicAPI_IsValidFullSHA(t *testing.T) { func TestSpec_PublicAPI_FindGitRoot(t *testing.T) { t.Run("returns non-empty absolute path when in git repository", func(t *testing.T) { root, err := FindGitRoot() - assert.NoError(t, err, "FindGitRoot should not error when inside a git repository") + require.NoError(t, err, "FindGitRoot should not error when inside a git repository") assert.NotEmpty(t, root, "FindGitRoot should return a non-empty path") assert.True(t, filepath.IsAbs(root), "FindGitRoot should return an absolute path, got %q", root) @@ -295,7 +296,7 @@ func TestSpec_PublicAPI_ReadFileFromHEADWithRoot(t *testing.T) { t.Run("reads known file from HEAD without error", func(t *testing.T) { content, err := ReadFileFromHEADWithRoot(filepath.Join(root, "go.mod"), root) - assert.NoError(t, err, "ReadFileFromHEADWithRoot should read go.mod without error") + require.NoError(t, err, "ReadFileFromHEADWithRoot should read go.mod without error") assert.NotEmpty(t, content, "content of go.mod should not be empty") })