Creates a PR with the agent’s code changes. Falls back to opening an issue if PR creation is blocked (e.g. org settings) — set fallback-as-issue: false to disable. Set max above 1 to allow multiple independent PRs per run.
safe-outputs:
create-pull-request:
title-prefix: "[ai] "# prefix for titles
labels: [automation] # labels to attach
reviewers: [user1, copilot] # reviewers (use 'copilot' for bot)
team-reviewers: [platform-reviewers] # team slugs to request as reviewers
assignees: [user1] # assignees for fallback issues
draft: true# create as draft — enforced as policy (default: true)
max: 3# max PRs per run (default: 1)
expires: 14# auto-close after N days (same-repo only; also accepts 2h, 7d, 2w, 1m, 1y)
if-no-changes: "warn"# "warn" (default), "error", or "ignore"
base-branch sets the PR’s target branch. Defaults to github.base_ref (PR event) or github.ref_name (push event). Use allowed-base-branches to let the agent pick the target branch at runtime — the agent supplies a base value in the tool call and it is accepted only if it matches one of the configured glob patterns.
allowed-branches restricts which source branch names the agent may use. The effective branch (agent-provided, or the checkout branch as fallback) must match a configured glob.
By default a random hex suffix is appended to the agent-provided branch name to avoid collisions. Set preserve-branch-name: true to omit the suffix (useful for repositories that enforce naming conventions such as Jira keys). If preserve-branch-name: true and the branch already exists on the remote, use recreate-ref: true to force-delete and recreate it (force-push semantics, intended for long-lived branches whose previous PR was already merged).
excluded-files strips matching files from the patch before the commit is created — they are also exempt from allowed-files and protected-files checks. max-patch-files (default 100) and max-patch-size (default 1024 KB) guard against unexpectedly large commits; raise them when the workflow intentionally produces many or large generated files.
draft is a policy, not a default — the agent cannot override it at runtime.
auto-close-issue (default true) appends Fixes #N to the PR description when the workflow is triggered from an issue. Set to false for partial-work or multi-PR flows.
When create-pull-request is configured, git commands (checkout, branch, switch, add, rm, commit, merge) are automatically enabled.
PRs do not trigger CI by default. See Triggering CI.
The agent’s commits are packaged as a git bundle and uploaded as an Actions artifact. A separate, permission-controlled safe_outputs job then:
Checks out the target repository at the base branch (shallow, depth 1). Any additional fetch: refs declared in checkout: frontmatter for the target repository are fetched so their commits are locally available.
Applies the bundle via git fetch <bundle-file>. If prerequisite commits are missing (because the base branch advanced while the agent was running), they are fetched from origin by SHA and the bundle fetch retried automatically.
Pushes the branch using the GitHub GraphQL API (signed commits) and creates the pull request.
If the base branch advances between agent start and safe_outputs apply, the PR is created slightly behind the current base — normal behavior the author can address with a rebase. If a non-fast-forward race occurs during the push itself, the job creates a fallback PR from a temporary branch so no changes are lost.
An older patch transport (git format-patch / git am --3way) is used when bundle data is unavailable. --3way resolves cleanly against an updated base when there are no conflicts; if it cannot, the patch is applied at the agent’s original base commit and the PR UI shows the conflicts for manual resolution.
Updates PR title or body. Both fields are enabled by default. The operation field controls how body updates are applied: append (default), prepend, or replace.
safe-outputs:
update-pull-request:
title: true# enable title updates (default: true)
body: true# enable body updates (default: true)
update-branch: false# update PR branch with latest base before other updates (default: false)
footer: false# omit AI-generated footer from body updates (default: true)
max: 1# max updates (default: 1)
target: "*"# "triggering" (default), "*", or number
target-repo: "owner/repo"# cross-repository
required-labels: [automated] # only update if PR has ALL these labels
required-title-prefix: "[bot] "# only update if PR title starts with this prefix
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }}# optional custom token for permissions
Target: "triggering" (requires PR event), "*" (any PR), or number (specific PR).
When update-branch: true is set, the handler calls the GitHub REST pulls.updateBranch API to merge the latest base branch changes into the PR branch before applying title or body updates. This requires contents: write permission; without it only contents: read is needed. The field can also be used alone (with title: false and body: false) to update the branch without changing the PR description.
If GitHub reports There are no new commits on the base branch. or merge conflict between base and head, the branch update is treated as best-effort: the workflow logs a warning and continues processing the safe output.
When using target: "*", the agent must provide pull_request_number in the output to identify which pull request to update.
Operation Types: Same as update-issue (append, prepend, replace). Title updates always replace the existing title. Disable fields by setting to false.
footer: "if-body"# footer control: "always", "none", or "if-body"
required-labels: [automated] # only comment if PR has ALL these labels
required-title-prefix: "[bot] "# only comment if PR title starts with this prefix
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }}# optional custom token for permissions
When target: "*" is configured, the agent must supply pull_request_number in each create_pull_request_review_comment tool call to identify which PR to comment on — omitting it will cause the comment to fail. For cross-repository scenarios, the agent can also supply repo (in owner/repo format) to route the comment to a PR in a different repository; the value must match target-repo or appear in allowed-repos.
required-labels: [automated] # only submit review if PR has ALL these labels
required-title-prefix: "[bot] "# only submit review if PR title starts with this prefix
Use allowed-events to control review decisions (APPROVE, COMMENT, REQUEST_CHANGES). Prefer allowed-events: [COMMENT] by default so bot reviews remain informative and non-blocking.
When you intentionally allow REQUEST_CHANGES, set supersede-older-reviews: true to dismiss older blocking reviews from the same workflow after posting a replacement review. This behavior is best-effort.
When target: "*" is configured, the agent must supply pull_request_number in each submit_pull_request_review tool call to identify which PR to review — omitting it will cause the review to fail. For cross-repository scenarios, the agent can also supply repo (in owner/repo format) to route the review to a PR in a different repository; the value must match target-repo or appear in allowed-repos.
Reply to PR Review Comment (reply-to-pull-request-review-comment:)
Replies to existing review comments on pull requests. Use this to respond to reviewer feedback, answer questions, or acknowledge comments. The comment_id must be the numeric ID of an existing review comment.
safe-outputs:
reply-to-pull-request-review-comment:
max: 10# max replies (default: 10)
target: "triggering"# "triggering" (default), "*", or number
required-labels: [automated] # only reply if PR has ALL these labels
required-title-prefix: "[bot] "# only reply if PR title starts with this prefix
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }}# optional custom token for permissions
The footer field controls whether AI-generated footers are added to PR review comments:
"always" (default) - Always include footer on review comments
"none" - Never include footer on review comments
"if-body" - Only include footer when the review has a body text
With footer: "if-body", approval reviews without body text appear clean without the AI-generated footer, while reviews with explanatory text still include the footer for attribution.
Resolves review threads on pull requests. Allows AI agents to mark review conversations as resolved after addressing the feedback. Uses the GitHub GraphQL API with the resolveReviewThread mutation.
By default, resolution is scoped to the triggering PR. Use target, target-repo, and allowed-repos for cross-repository thread resolution.
safe-outputs:
resolve-pull-request-review-thread:
max: 10# max threads to resolve (default: 10)
target: "triggering"# "triggering" (default), "*", or number
Pushes changes to a PR’s branch. Validates via required-title-prefix and required-labels to ensure only approved PRs receive changes. Multiple pushes per run are supported by setting max higher than 1.
safe-outputs:
push-to-pull-request-branch:
target: "*"# "triggering" (default), "*", or number
required-title-prefix: "[bot] "# require title prefix
required-labels: [automated] # require all labels
max: 3# max pushes per run (default: 1)
if-no-changes: "warn"# "warn" (default), "error", or "ignore"
excluded-files: # files to omit from the patch entirely
- "**/*.lock"
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }}# optional custom token for permissions
github-token-for-extra-empty-commit: ${{ secrets.CI_TOKEN }}# optional token to push empty commit triggering CI
fallback-as-pull-request: true# on non-fast-forward failure, create fallback PR to original PR branch (default: true)
signed-commits: true# signed commits are required (default); set false to use git push directly
ignore-missing-branch-failure: false# treat deleted/missing branch errors as skipped instead of failed (default: false)
check-branch-protection: true# set to false to skip the branch protection pre-flight check (default: true)
protected-files: fallback-to-issue# create review issue if protected files modified
target-repo: "owner/repo"# cross-repository (target repo must be checked out)
When push-to-pull-request-branch is configured, git commands (checkout, branch, switch, add, rm, commit, merge) are automatically enabled.
By default, pushes are replayed through GitHub’s signed commit API because signed-commits: true means signed commits are required. Set signed-commits: false only for repositories that do not require signed commits; this uses direct git push and can preserve merge commits that the signed commit API cannot represent. This field is supported by both create-pull-request and push-to-pull-request-branch.
push-to-pull-request-branch supports pushing to pull requests in a different repository via target-repo (and optionally allowed-repos). When target-repo is set, the target repository must be checked out into the workflow workspace using the checkout: frontmatter field with a path: specified. Use target-repo: "*" to let the agent choose the target repository at runtime (the safe_outputs job will check out all checkout: repositories into subdirectories automatically).
checkout:
- fetch-depth: 0# checkout current (source) repo
- repository: org/target-repo
path: ./target-repo# must set path for cross-repo checkout
github-token: ${{ secrets.CROSS_REPO_PAT }}
fetch: ["refs/pulls/open/*"] # fetch all open PR branches
safe-outputs:
github-token: ${{ secrets.CROSS_REPO_PAT }}
push-to-pull-request-branch:
target-repo: "org/target-repo"
required-title-prefix: "[bot] "
The path: field is required so the agent knows where the target repository is mounted in the workspace. Without a path, the checkout action writes to the root of the workspace and overwrites the source repository, which will cause the workflow to fail.
See Cross-Repository Operations for a complete example and documentation on target-repo, allowed-repos, and cross-repository authentication.
Like create-pull-request, pushes with GitHub Agentic Workflows do not trigger CI. See Triggering CI for how to enable automatic CI triggers.
Adds reviewers to pull requests. Specify allowed-reviewers to restrict to specific GitHub usernames and allowed-team-reviewers to restrict to specific team slugs.
safe-outputs:
add-reviewer:
allowed-reviewers: [user1, copilot] # restrict to specific user/bot reviewers
allowed-team-reviewers: [platform-reviewers] # restrict to specific team reviewers
max: 3# max reviewers (default: 3)
target: "*"# "triggering" (default), "*", or number
target-repo: "owner/repo"# cross-repository
required-labels: [automated] # only add reviewer if PR has ALL these labels
required-title-prefix: "[bot] "# only add reviewer if PR title starts with this prefix
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }}# optional custom token for permissions
Target: "triggering" (requires PR event), "*" (any PR), or number (specific PR).
Use allowed-reviewers: [copilot] to assign the Copilot PR reviewer bot. See Assign to Agent.
When target: "*" is used, gh aw compile emits warnings for two common misconfigurations:
Missing wildcard fetch — no checkout block with a wildcard fetch pattern (e.g., fetch: ["*"]). Without this, the agent cannot access arbitrary PR branches at runtime and will fail with permission-like errors.
No constraints — neither required-title-prefix nor required-labels is set, which allows pushing to any PR in the repository with no additional gating.
Both warnings are suppressed when the recommended configuration is in place:
If push-to-pull-request-branch (or create-pull-request) fails, the safe-output pipeline cancels all remaining non-code-push outputs. Each cancelled output is marked with an explicit reason such as “Cancelled: code push operation failed”. The failure details appear in the agent failure issue or comment generated by the conclusion job.
When fallback-as-pull-request is enabled (default), non-fast-forward push failures trigger a fallback pull request that targets the original PR branch. Set fallback-as-pull-request: false to disable this fallback behavior.
When ignore-missing-branch-failure: true is set, push failures caused by a deleted or missing PR branch return skipped: true instead of a hard failure. This is useful when the PR branch may have been deleted before the safe-output job runs (for example, on auto-merged PRs). Without this flag, a missing branch is a terminal error.
When check-branch-protection: false is set, the branch protection API pre-flight check is skipped. By default (true), the handler calls GET /repos/{owner}/{repo}/branches/{branch}/protection before pushing to detect whether the target branch is protected. This API call requires administration: read. If the token lacks that permission, the check logs a warning and continues (the GitHub platform still enforces protection at push time). Set check-branch-protection: false to suppress the warning and avoid the API call entirely.
Both create-pull-request and push-to-pull-request-branch enforce protected file protection by default. Patches that modify package manifests, agent instruction files, repository security configuration, or any top-level directory whose name starts with . are refused unless you explicitly configure a policy.
This protects against supply chain attacks where an AI agent could inadvertently (or through prompt injection) alter dependency definitions, CI/CD pipelines, or agent behavior files.
The following are always protected regardless of policy (unless explicitly excluded):
Package manifests: package.json, go.mod, go.sum, Gemfile, Pipfile, pyproject.toml, and other runtime lockfiles.
Security configuration: CODEOWNERS, DESIGN.md.
Agent instruction files: AGENTS.md, CLAUDE.md, GEMINI.md, and other engine-specific instruction files.
Common top-level documentation: README.md, CONTRIBUTING.md, CHANGELOG.md, SECURITY.md, CODE_OF_CONDUCT.md. These files are frequently imported by agents as context, so they are protected by default.
Specific protected directories: .github/, .agents/, .githooks/, .husky/.
Any top-level directory starting with .: for example .cursor/, .vscode/, .devcontainer/, or any other hidden configuration directory at the repository root. This rule catches newly-created dot-directories without requiring an explicit list update.
The protected-files field accepts either a string policy value or an object with a policy and an exclude list.
String form — set a single policy for all protected files:
Value
Behavior
request_review (default)
Create the pull request and submit a REQUEST_CHANGES review listing the protected files. The agent’s work is preserved, and a human reviewer must approve before merge.
blocked
Hard-block: the safe output fails with an error
fallback-to-issue
Create a review issue with instructions for the human to apply or reject the changes manually
allowed
No restriction — all protected file changes are permitted. Use only when the workflow is explicitly designed to manage these files.
Object form — set a policy and exclude specific files from the protected set:
safe-outputs:
create-pull-request:
protected-files:
policy: fallback-to-issue# same values as string form (default: request_review)
exclude:
- AGENTS.md# allow the agent to update its own instruction file
- CHANGELOG.md# allow the agent to update the changelog
- .agents/# allow updates to the .agents/ directory
- .cursor/# allow updates to the .cursor/ directory
The exclude list names files by basename (e.g., AGENTS.md) or path prefix (e.g., .agents/) to remove from the default protected set. Dot-folder path prefixes in the exclude list (e.g. .cursor/) also opt that directory out of the general top-level-dot-folder protection rule. The remaining protected files still enforce the configured policy. This is useful when a workflow is explicitly designed to manage one specific instruction file or configuration directory without disabling all protection.
create-pull-request with fallback-to-issue: when protected files are detected, gh-aw skips pushing and creates a review issue with a PR creation intent link, a [!WARNING] banner explaining why the fallback was triggered, and instructions to review carefully before creating the PR.
push-to-pull-request-branch with fallback-to-issue: instead of pushing to the PR branch, a review issue is created with the target PR link, patch download/apply instructions, and a review warning.
safe-outputs:
create-pull-request:
protected-files: fallback-to-issue# skip push and require human review before PR
push-to-pull-request-branch:
protected-files: fallback-to-issue# create issue instead of pushing when protected files change
When protected file protection triggers and is set to blocked, the Protected Files section appears in the agent failure issue or comment generated by the conclusion job. It includes the blocked operation, the specific files found, and a YAML remediation snippet showing how to configure protected-files: fallback-to-issue.
Parameterizing Policy Fields in Reusable Workflows
Both protected-files and patch-format accept GitHub Actions expression strings so that reusable workflow_call workflows can let callers choose the policy without duplicating the workflow file.
on:
workflow_call:
inputs:
protected-files-policy:
type: string
default: fallback-to-issue
description: >
Protected-file policy: 'request_review', 'blocked', 'fallback-to-issue', or 'allowed'.
patch-format:
type: string
default: bundle
description: Transport format: 'bundle'(default) or 'am'.
Literal values are still validated at compile time. Expression strings are passed through to the runtime config where they are evaluated by GitHub Actions before the handler runs. If the resolved value is not one of the documented allowed values, the handler fails closed:
protected-files: an unrecognized resolved value is treated as blocked (deny — most restrictive).
patch-format: an unrecognized resolved value results in an explicit error before any git operations.
The object form of protected-files also accepts an expression for policy:
safe-outputs:
create-pull-request:
protected-files:
policy: ${{ inputs.protected-files-policy }}
exclude:
- AGENTS.md# always exclude — regardless of policy
Restricting Changes to Specific Files with allowed-files
Use allowed-files to restrict a safe output to a fixed set of files. When set, it acts as an exclusive allowlist: every file touched by the patch must match at least one pattern, and any file outside the list is always refused — including normal source files. The allowed-files and protected-files checks are orthogonal: both run independently and both must pass. To modify a protected file, it must both match allowed-filesandprotected-files must be set to allowed.
[!CAUTION]
allowed-files is an exclusive allowlist, not an “additionally allow” list. Setting allowed-files: [".github/workflows/*"] blocks all other files, including normal source code like src/**. If you want to allow .github/workflows/* alongside regular source files, you must list every pattern explicitly:
allowed-files:
- .github/workflows/*
- src/**
Files not listed are refused regardless of whether they are normally unprotected.
safe-outputs:
push-to-pull-request-branch:
allowed-files:
- .changeset/**# only changeset files may be pushed
create-pull-request:
allowed-files:
- .github/aw/instructions.md# only this one file may be modified
Patterns support * (any characters except /) and ** (any characters including /):
Pattern
Matches
go.mod
Exactly go.mod at the repository root (full path comparison)
*.json
Any JSON file at the root (e.g. package.json)
go.*
go.mod, go.sum, etc. at the root
.github/**
All files under .github/ at any depth
.github/workflows/*.yml
Only YAML files directly in .github/workflows/
**/package.json
package.json at any path depth
[!NOTE]
When allowed-files is not set, only the protected-files policy applies and all non-protected files are permitted.
Allowing Workflow File Changes with allow-workflows
When allowed-files targets .github/workflows/ paths, pushing to those paths requires the GitHub Actions workflows permission. This is a GitHub App-only permission — it cannot be granted via GITHUB_TOKEN.
Set allow-workflows: true on create-pull-request or push-to-pull-request-branch to add workflows: write to the minted GitHub App token. A safe-outputs.github-app configuration is required; the compiler will error if allow-workflows: true is set without one.
safe-outputs:
github-app:
client-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
create-pull-request:
allow-workflows: true
allowed-files:
- ".github/workflows/*.lock.yml"
protected-files: allowed
[!NOTE]
allow-workflows is intentionally explicit rather than auto-inferred from allowed-files patterns. This makes the elevated permission visible and auditable in the workflow source.
4. Repository governance files — matched by filename anywhere in the repository:
File
Description
CODEOWNERS
Governs required code reviewers; valid at the repository root, .github/, or docs/
DESIGN.md
Defines persistent design-system guidance for coding agents
[!NOTE]
Runtime manifests and governance files (CODEOWNERS, DESIGN.md) are matched by basename only (the filename without its directory path), so they are protected regardless of where they appear in the repository. Path-prefix rules (.github/, .agents/, .githooks/, .husky/, .claude/, .codex/) match the full relative path from the repository root.