GitHub Agentic Workflows

Safe Outputs (Pull Requests)

This page is the primary reference for pull-request-focused safe outputs:

Code-writing types (create-pull-request and push-to-pull-request-branch) enforce Protected Files by default.

For all other safe-output types see Safe Outputs.

Pull Request Creation (create-pull-request:)

Section titled “Pull Request Creation (create-pull-request:)”

Creates PRs with code changes. By default, falls back to creating an issue if PR creation fails (e.g., org settings block it). Set fallback-as-issue: false to disable this fallback and avoid requiring issues: write permission. expires field (same-repo only) auto-closes after period: integers (days) or 2h, 7d, 2w, 1m, 1y (hours < 24 treated as 1 day).

Multiple PRs per run are supported by setting max higher than 1. Each PR is created from its own branch with an independent patch, so concurrent calls do not conflict.

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 (including protected-files and PR creation failure fallbacks)
draft: true # create as draft — enforced as policy (default: true)
max: 3 # max PRs per run (default: 1)
expires: 14 # auto-close after 14 days (same-repo only)
if-no-changes: "warn" # "warn" (default), "error", or "ignore"
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories
base-branch: "vnext" # target branch for PR (default: github.base_ref || github.ref_name)
allowed-base-branches: # allow agent to override base branch at runtime (glob patterns)
- main
- release/*
fallback-as-issue: false # disable issue fallback (default: true)
auto-close-issue: false # don't auto-add "Fixes #N" to PR description (default: true)
preserve-branch-name: true # omit random salt suffix from branch name (default: false)
excluded-files: # files to omit from the patch entirely
- "**/*.lock"
- "dist/**"
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
protected-files: fallback-to-issue # push branch, create review issue if protected files modified

The base-branch field specifies which branch the pull request should target. This is particularly useful for cross-repository PRs where you need to target non-default branches (e.g., vnext, release/v1.0, staging). When not specified, defaults to github.base_ref (the PR’s target branch) with a fallback to github.ref_name (the workflow’s branch) for push events.

The allowed-base-branches field enables per-run base branch overrides by the agent at runtime. When configured, the agent may supply a base field in the create_pull_request tool call to target a branch other than the compiled base-branch. The override is accepted only when it matches one of the configured glob patterns (e.g., main, release/*). Without allowed-base-branches, only the compiled base-branch is used regardless of what the agent requests. This is useful when agent-computed data (such as a version string or user request) determines the target branch at runtime:

safe-outputs:
create-pull-request:
base-branch: main
allowed-base-branches:
- main
- release/*

Example use case: A workflow in org/engineering that creates PRs in org/docs targeting the vnext branch for feature documentation:

safe-outputs:
create-pull-request:
target-repo: "org/docs"
base-branch: "vnext"
draft: true
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }} # optional custom token for permissions

The excluded-files field accepts a list of glob patterns. Each matching file is stripped from the patch using git format-patch’s :(exclude) magic pathspec at generation time, so the file never appears in the commit. Excluded files are also exempt from allowed-files and protected-files checks. This is useful for suppressing auto-generated or lock files that the agent must not commit (e.g. **/*.lock, dist/**). Supports * (any characters except /) and ** (any characters including /).

The preserve-branch-name field, when set to true, omits the random hex salt suffix that is normally appended to the agent-specified branch name. This is useful when the target repository enforces branch naming conventions such as Jira keys in uppercase (e.g., bugfix/BR-329-red instead of bugfix/br-329-red-cde2a954). Invalid characters are always replaced for security, and casing is always preserved regardless of this setting. Defaults to false.

When preserve-branch-name: true and the agent-supplied branch name already exists on the remote, the workflow fails with an explicit error rather than silently appending a random suffix. To resolve, delete the existing remote branch, choose a different branch name, or disable preserve-branch-name to allow collision-avoidance via a random suffix.

The draft field is a configuration policy, not a default. Whatever value is set in the workflow frontmatter is always used — the agent cannot override it at runtime.

By default, when a workflow is triggered from an issue, the create-pull-request handler automatically appends - Fixes #N to the PR description if no closing keyword is already present. This causes GitHub to auto-close the triggering issue when the PR is merged. Set auto-close-issue: false to opt out of this behavior — useful for partial-work PRs, multi-PR workflows, or any case where the PR should reference but not close the issue.

PR creation may fail if “Allow GitHub Actions to create and approve pull requests” is disabled in Organization Settings. By default (fallback-as-issue: true), fallback creates an issue with branch link. Set fallback-as-issue: false to disable fallback.

When create-pull-request is configured, git commands (checkout, branch, switch, add, rm, commit, merge) are automatically enabled.

By default, PRs created with GitHub Agentic Workflows do not trigger CI. See Triggering CI for how to configure CI triggers.

When the coding agent finishes its task, it records the requested changes in a structured output file. A separate, permission-controlled job then reads that output and applies the changes:

  1. The agent’s commits are exported as a git format-patch file covering everything since the original checkout commit.
  2. The safe-output job checks out the target repository and fetches the latest state of the base branch.
  3. The patch is applied to a new branch using git am --3way. The --3way flag allows the patch to succeed even when the agent’s source repository differs from the target (for example, in cross-repository workflows).
  4. The branch is pushed and the GitHub API creates the pull request.

If commits have been pushed to the base branch after the agent started, two outcomes are possible:

  • No conflictsgit am --3way resolves the patch cleanly against the updated base. The PR is created normally and targets the current head of the base branch.
  • Conflicts — if --3way cannot resolve the conflicts automatically, the safe-output job falls back to applying the patch at the commit the agent originally branched from. The PR is created with the branch based on that earlier commit, and GitHub’s pull request UI shows the conflicts for manual resolution.

Pull Request Updates (update-pull-request:)

Section titled “Pull Request Updates (update-pull-request:)”

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

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.

Closes PRs without merging with optional comment. Filter by labels and title prefix. Target: "triggering" (PR event), "*" (any), or number.

safe-outputs:
close-pull-request:
target: "triggering" # "triggering" (default), "*", or number
required-labels: [automated, stale] # only close with these labels
required-title-prefix: "[bot]" # only close matching prefix
max: 10 # max closures (default: 1)
target-repo: "owner/repo" # cross-repository
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }} # optional custom token for permissions

PR Review Comments (create-pull-request-review-comment:)

Section titled “PR Review Comments (create-pull-request-review-comment:)”

Creates review comments on specific code lines in PRs. Supports single-line and multi-line comments.

safe-outputs:
create-pull-request-review-comment:
max: 3 # max comments (default: 10)
side: "RIGHT" # "LEFT" or "RIGHT" (default: "RIGHT")
target: "*" # "triggering" (default), "*", or number
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories
footer: "if-body" # footer control: "always", "none", or "if-body"
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.

Submit PR Review (submit-pull-request-review:)

Section titled “Submit PR Review (submit-pull-request-review:)”

Submits a consolidated pull request review. Inline comments buffered by create-pull-request-review-comment are included automatically.

safe-outputs:
submit-pull-request-review:
max: 1
allowed-events: [COMMENT, REQUEST_CHANGES] # include REQUEST_CHANGES when superseding older blocking reviews
supersede-older-reviews: true # dismiss older same-workflow REQUEST_CHANGES reviews after replacement
target: "triggering" # or "*", or explicit PR number
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/repo1"] # additional allowed repositories
footer: "always" # "always", "none", or "if-body"

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.

Reply to PR Review Comment (reply-to-pull-request-review-comment:)

Section titled “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
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/other-repo"] # additional allowed repositories
footer: true # add AI-generated footer (default: true)
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.

Resolve PR Review Thread (resolve-pull-request-review-thread:)

Section titled “Resolve PR Review Thread (resolve-pull-request-review-thread:)”

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
target-repo: "owner/repo" # cross-repository
allowed-repos: ["org/repo1", "org/repo2"] # additional allowed repositories
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }} # optional custom token for permissions

See Cross-Repository Operations for documentation on target-repo, allowed-repos, and cross-repository authentication.

Agent output format:

{"type": "resolve_pull_request_review_thread", "thread_id": "PRRT_kwDOABCD..."}

Push to PR Branch (push-to-pull-request-branch:)

Section titled “Push to PR Branch (push-to-pull-request-branch:)”

Pushes changes to a PR’s branch. Validates via title-prefix and 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
title-prefix: "[bot] " # require title prefix
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)
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)
allowed-repos: ["org/repo1"] # additional allowed repositories

When push-to-pull-request-branch is configured, git commands (checkout, branch, switch, add, rm, commit, merge) are automatically enabled.

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.

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"
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 reviewers to restrict to specific GitHub usernames and team-reviewers to restrict to specific team slugs.

safe-outputs:
add-reviewer:
reviewers: [user1, copilot] # restrict to specific user/bot reviewers
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
github-token: ${{ secrets.SOME_CUSTOM_TOKEN }} # optional custom token for permissions

Target: "triggering" (requires PR event), "*" (any PR), or number (specific PR).

Use 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 title-prefix nor 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:

safe-outputs:
push-to-pull-request-branch:
target: "*"
title-prefix: "[bot] "
checkout:
fetch: ["*"]
fetch-depth: 0

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 behaviour 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, and other engine-specific instruction files.
  • 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:

ValueBehaviour
blocked (default)Hard-block: the safe output fails with an error
fallback-to-issueCreate a review issue with instructions for the human to apply or reject the changes manually
allowedNo 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: blocked)
exclude:
- AGENTS.md # allow the agent to update its own instruction file
- .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: the branch is pushed normally, then a review issue is created 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 # push branch, 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.

Restricting Changes to Specific Files with allowed-files

Section titled “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-files and protected-files must be set to allowed.

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 /):

PatternMatches
go.modExactly go.mod at the repository root (full path comparison)
*.jsonAny 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/*.ymlOnly YAML files directly in .github/workflows/
**/package.jsonpackage.json at any path depth

Allowing Workflow File Changes with allow-workflows

Section titled “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

Protection covers three categories:

1. Runtime dependency manifests — matched by filename anywhere in the repository:

RuntimeProtected files
Node.js (npm)package.json, package-lock.json, yarn.lock, pnpm-lock.yaml, npm-shrinkwrap.json
Node.js (Bun)package.json, bun.lockb, bunfig.toml
Denodeno.json, deno.jsonc, deno.lock
Gogo.mod, go.sum
Python (pip/setuptools)requirements.txt, Pipfile, Pipfile.lock, pyproject.toml, setup.py, setup.cfg
Python (uv)pyproject.toml, uv.lock
RubyGemfile, Gemfile.lock
Java (Maven)pom.xml
Java (Gradle)build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradle.properties
Elixirmix.exs, mix.lock
Haskellstack.yaml, stack.yaml.lock
.NETglobal.json, NuGet.Config, Directory.Packages.props

2. Engine instruction files — added automatically based on the active AI engine:

EngineProtected filesProtected directories
Copilot (default)AGENTS.md
ClaudeCLAUDE.md.claude/
CodexAGENTS.md.codex/

3. Repository security configuration — matched by path prefix:

  • .github/ — covers all GitHub Actions workflows, Dependabot config, and other repository-level security settings.
  • .agents/ — covers generic agent instruction and configuration files stored in the .agents/ directory.
  • .githooks/ — covers repository-tracked git hook scripts.
  • .husky/ — covers Husky-managed git hook scripts.

4. Repository governance files — matched by filename anywhere in the repository:

FileDescription
CODEOWNERSGoverns required code reviewers; valid at the repository root, .github/, or docs/
DESIGN.mdDefines persistent design-system guidance for coding agents