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
30 changes: 30 additions & 0 deletions .github/aw/create-agentic-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,36 @@ This gives users the choice of triggering via comment (`/deploy`) or via label,
- `slash_command` full reference: https://github.github.com/gh-aw/reference/command-triggers/
- `label_command` and LabelOps: https://github.github.com/gh-aw/patterns/label-ops/

## Creating Monitoring Workflows
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot unbloat the guidance in this file, keep minimal changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed the section to a single minimal example with just the conclusion filter syntax and valid values. Removed the context table, Example 2, when-to-use checklist, and guiding-the-user paragraph. (9d509a5)


Use `workflow_run` to react to CI/CD pipelines in the same repository. Set `on.workflow_run.conclusion` to filter by result — the compiler converts it into a job `if` condition automatically.

```aw wrap
---
on:
workflow_run:
workflows: ["CI"]
types: [completed]
conclusion: failure # or: [failure, timed_out]
permissions:
contents: read
tools:
github:
toolsets: [default]
safe-outputs:
add-comment:
max: 1
---

The CI workflow failed for branch `${{ github.event.workflow_run.head_branch }}`.

Use the GitHub MCP tools to find the open pull request for branch `${{ github.event.workflow_run.head_branch }}`. Post a concise comment on that PR summarising the failure and suggesting next steps for the author.
```

Valid conclusion values: `success`, `failure`, `cancelled`, `skipped`, `timed_out`, `action_required`, `neutral`, `stale`.

> ⚠️ `workflow_run` only works for workflows in the **same repository**. Use `deployment_status` for external deployment services.

## Best Practices

### Improver Coding Agents in Large Repositories
Expand Down
9 changes: 9 additions & 0 deletions actions/setup/js/aw_context.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ function resolveItemContext(payload) {
* comment_id: string,
* comment_node_id: string,
* deployment_state: string,
* workflow_run_conclusion: string,
* otel_trace_id: string,
* otel_parent_span_id: string,
* trigger_label: string
Expand All @@ -128,6 +129,10 @@ function resolveItemContext(payload) {
* "success") when the workflow was triggered by a deployment_status event.
* Empty string for all other event types. Propagated to child workflows via
* workflow_call so they can identify which state triggered the parent.
* - workflow_run_conclusion: The conclusion of the triggering workflow_run
* (e.g. "failure", "success", "cancelled", "timed_out") when the workflow was
* triggered by a workflow_run event. Empty string for all other event types.
* Propagated to child workflows via workflow_call.
* - otel_trace_id: OTLP trace ID from the parent workflow's setup span.
* Empty string when OTLP is not configured or the parent setup step has
* not yet run. Used by child workflow setup steps to continue the same
Expand Down Expand Up @@ -163,6 +168,10 @@ function buildAwContext() {
// triggering event is deployment_status. Empty string for all other events.
// Propagated to called workflows so they can access the deployment state.
deployment_state: context.eventName === "deployment_status" ? (context.payload?.deployment_status?.state ?? "") : "",
// workflow_run_conclusion carries the conclusion of the triggering workflow_run
// when the event is workflow_run. Empty string for all other events.
// Propagated to called workflows so they can access the workflow run conclusion.
workflow_run_conclusion: context.eventName === "workflow_run" ? (context.payload?.workflow_run?.conclusion ?? "") : "",
// Propagate the current OTLP trace ID to dispatched child workflows so that
// composite actions share the same trace as their parent. Empty string when
// OTLP is not configured or the parent setup step has not run yet.
Expand Down
8 changes: 8 additions & 0 deletions actions/setup/js/generate_aw_info.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ async function main(core, ctx) {
awInfo.deployment_state = deploymentState;
}

// Include workflow_run_conclusion when triggered by a workflow_run event.
// This makes the triggering run conclusion available to the agent without requiring it
// to read the raw event payload, and is propagated to child workflows via aw_context.
const workflowRunConclusion = ctx.payload?.workflow_run?.conclusion;
if (workflowRunConclusion && typeof workflowRunConclusion === "string") {
awInfo.workflow_run_conclusion = workflowRunConclusion;
}

// Include custom token weights when set (engine.token-weights in workflow frontmatter).
// Deep structure validation is intentionally minimal here: the JSON schema and Go parser
// already validate the structure at compile time. We only verify the top-level type to
Expand Down
10 changes: 10 additions & 0 deletions actions/setup/js/send_otlp_span.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,11 @@ async function sendJobSetupSpan(options = {}) {
if (deploymentStateSetup) {
attributes.push(buildAttr("gh-aw.deployment.state", deploymentStateSetup));
}
// Workflow run conclusion: from aw_info or aw_context propagation.
const workflowRunConclusion = (typeof awInfo.workflow_run_conclusion === "string" ? awInfo.workflow_run_conclusion : "") || (typeof awInfo.context?.workflow_run_conclusion === "string" ? awInfo.context.workflow_run_conclusion : "");
if (workflowRunConclusion) {
attributes.push(buildAttr("gh-aw.workflow_run.conclusion", workflowRunConclusion));
}
attributes.push(buildAttr("gh-aw.staged", staged));
if (itemType) attributes.push(buildAttr("gh-aw.trigger.item_type", itemType));
if (itemNumber) attributes.push(buildAttr("gh-aw.trigger.item_number", itemNumber));
Expand Down Expand Up @@ -775,6 +780,11 @@ async function sendJobConclusionSpan(spanName, options = {}) {
if (deploymentStateConclusion) {
attributes.push(buildAttr("gh-aw.deployment.state", deploymentStateConclusion));
}
// Workflow run conclusion: from aw_info or aw_context propagation.
const workflowRunConclusion = (typeof awInfo.workflow_run_conclusion === "string" ? awInfo.workflow_run_conclusion : "") || (typeof awInfo.context?.workflow_run_conclusion === "string" ? awInfo.context.workflow_run_conclusion : "");
if (workflowRunConclusion) {
attributes.push(buildAttr("gh-aw.workflow_run.conclusion", workflowRunConclusion));
}
attributes.push(buildAttr("gh-aw.staged", staged));
if (itemType) attributes.push(buildAttr("gh-aw.trigger.item_type", itemType));
if (itemNumber) attributes.push(buildAttr("gh-aw.trigger.item_number", itemNumber));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# ADR-29089: Compile `on.workflow_run.conclusion` Filter into Job `if` Condition at Build Time

**Date**: 2026-04-29
**Status**: Draft
**Deciders**: pelikhan, Copilot

---

## Part 1 — Narrative (Human-Friendly)

### Context

GitHub Actions' `workflow_run` trigger fires for any conclusion value (`success`, `failure`, `cancelled`, etc.) when `types: [completed]` is specified — there is no native way to filter by conclusion in the `on:` block. Users who want to react only to failed runs must write a `if:` expression guard, and that guard requires an additional `github.event_name != 'workflow_run'` prefix so the condition remains transparent when the workflow fires from other events (e.g., `workflow_dispatch`). This two-part guard is subtle, error-prone to hand-write, and inconsistent with the simpler `on.deployment_status.state` pattern the compiler already handles.

### Decision

We will add `on.workflow_run.conclusion: string | string[]` as a recognized frontmatter field and compile it at build time into a guarded GitHub Actions expression that is AND-merged with any existing `if:` condition on the activation job. The generated guard takes the form `github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == '<value>')`, mirroring the pattern already established for `on.deployment_status.state`. The raw `conclusion:` field is commented out in the compiled YAML `on:` section to make clear it is not a native GitHub Actions key.

### Alternatives Considered

#### Alternative 1: Require Users to Write Manual `if:` Conditions

Users would write `if: github.event.workflow_run.conclusion == 'failure'` directly in frontmatter. This was rejected because it omits the event_name guard, which silently breaks workflows that also respond to `workflow_dispatch` or other non-`workflow_run` triggers — the conclusion expression evaluates to false for those events, preventing the workflow from running at all. The correct two-part guard is non-obvious and would require documentation warnings; the compiler can generate it correctly every time instead.

#### Alternative 2: Runtime Filtering in the Agent Context Script

The `aw_context.cjs` setup action could check `workflow_run.conclusion` at runtime and exit early if it doesn't match. This was rejected because it still consumes GitHub Actions runner minutes to start the job before aborting, and it moves filtering logic into the runtime layer rather than the declarative compilation layer where similar patterns already live (`deployment_status.state`).

### Consequences

#### Positive
- Users declare intent declaratively (`conclusion: failure`) without needing to understand GitHub Actions expression syntax or the event_name guard pattern.
- Consistent with the existing `on.deployment_status.state` compiler feature, keeping the frontmatter DSL coherent and the compilation logic concentrated in one place.
- The event_name guard is generated correctly and automatically, eliminating a class of silent bugs.
- `workflow_run_conclusion` is propagated to child workflows via `aw_context`, making the triggering conclusion visible to the agent without reading the raw event payload.

#### Negative
- Adds stateful flag complexity (`inWorkflowRun`, `inWorkflowRunConclusionArray`) to `commentOutProcessedFieldsInOnSection`, which already manages multiple section flags that can interact unexpectedly.
- The set of valid conclusion values (`success`, `failure`, `cancelled`, `skipped`, `timed_out`, `action_required`, `neutral`, `stale`) is documented only in the user-facing docs; if GitHub adds new conclusion values, the documentation must be updated manually.
- The compiler now silently transforms the `conclusion:` key — users inspecting compiled YAML must understand it has been moved to a job condition.

#### Neutral
- The `workflow_run_conclusion` field is also added to `awInfo` and the OTEL spans, which slightly increases the payload size for all `workflow_run`-triggered runs.
- Tests for the new logic are in a dedicated file (`workflow_run_conclusion_test.go`), consistent with how the deployment_status tests are organized.

---

## Part 2 — Normative Specification (RFC 2119)

> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).

### Frontmatter Compilation

1. When `on.workflow_run.conclusion` is present in the workflow frontmatter, the compiler **MUST** convert it into a GitHub Actions job `if` expression of the form `github.event_name != 'workflow_run' || (<conclusion-expr>)`.
2. When `on.workflow_run.conclusion` is a string, `<conclusion-expr>` **MUST** be `github.event.workflow_run.conclusion == '<value>'`.
3. When `on.workflow_run.conclusion` is an array of strings, `<conclusion-expr>` **MUST** be the individual equality checks joined with ` || `.
4. The generated condition **MUST** be AND-merged with any existing `if:` expression from the frontmatter using the form `(<existing>) && (<conclusion-condition>)`.
5. The compiler **MUST** comment out the `conclusion:` key (and any array items beneath it) in the compiled YAML `on:` section, appending an explanatory comment.
6. The compiler **MUST NOT** comment out sibling keys of `workflow_run` (such as `workflows:` or `types:`) as a side effect of processing `conclusion:`.
7. If `on.workflow_run.conclusion` is absent or empty, the compiler **MUST NOT** add any conclusion condition to the job `if` expression.

### Agent Context Propagation

1. When a workflow is triggered by a `workflow_run` event, `buildAwContext()` **MUST** populate `workflow_run_conclusion` with the value of `context.payload.workflow_run.conclusion`.
2. For all other event types, `workflow_run_conclusion` **MUST** be set to an empty string.
3. Implementations **MUST** propagate `workflow_run_conclusion` to child workflows via `workflow_call` inputs, following the same pattern as `deployment_state`.
4. The `workflow_run_conclusion` field **SHOULD** be included in `awInfo` so agents can access the conclusion without reading the raw event payload.

### OTEL Instrumentation

1. When `workflow_run_conclusion` is non-empty, implementations **MUST** include a `gh-aw.workflow_run.conclusion` attribute on both the setup span and the conclusion span.
2. Implementations **MUST** read `workflow_run_conclusion` from `awInfo.workflow_run_conclusion` first, falling back to `awInfo.context.workflow_run_conclusion` for child workflows that received it via propagation.

### Conformance

An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance.

---

*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/25108336032) workflow. The PR author must review, complete, and finalize this document before the PR can merge.*
4 changes: 3 additions & 1 deletion pkg/workflow/compiler_orchestrator_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ func (c *Compiler) ParseWorkflowFile(markdownPath string) (*WorkflowData, error)
workflowData.ActionPinWarnings = c.actionPinWarnings

// Extract YAML configuration sections from frontmatter
c.extractYAMLSections(result.Frontmatter, workflowData)
if err := c.extractYAMLSections(result.Frontmatter, workflowData); err != nil {
return nil, formatCompilerError(cleanPath, "error", err.Error(), err)
}

// Merge observability config from imports into RawFrontmatter so that injectOTLPConfig
// can see an OTLP endpoint defined in an imported workflow (first-wins from imports).
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/compiler_string_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ func (c *Compiler) ParseWorkflowString(content string, virtualPath string) (*Wor
workflowData.ActionPinWarnings = c.actionPinWarnings

// Extract YAML configuration sections
c.extractYAMLSections(parseResult.frontmatterResult.Frontmatter, workflowData)
if err := c.extractYAMLSections(parseResult.frontmatterResult.Frontmatter, workflowData); err != nil {
return nil, fmt.Errorf("failed to extract YAML sections: %w", err)
}

// Merge features from imports
if len(engineSetup.importsResult.MergedFeatures) > 0 {
Expand Down
Loading