| name | gh-aw-guide |
| description | Comprehensive guide for building and maintaining GitHub Agentic Workflows (gh-aw). Covers architecture, security boundaries, fork handling, safe outputs, anti-patterns, compilation, and troubleshooting. Use when creating or editing gh-aw workflow .md files, writing safe-outputs configurations, configuring fork PR handling, setting up integrity filtering, debugging "why doesn't my workflow trigger", or any task involving .github/workflows/*.md or .lock.yml files. Also use when asked about gh-aw features, slash commands, pre-agent-steps, protected files, or agentic workflow security. |
gh-aw (GitHub Agentic Workflows) Guide
This skill provides a complete reference for building, securing, and maintaining GitHub Agentic Workflows. It covers the gh-aw platform's architecture, security model, and all available features.
Version baseline: This guide covers features through v0.71.1. A fresh gh extension install github/gh-aw gets the latest release. Features introduced after v0.68.3 are marked with their minimum version (e.g., (v0.70.0+)). Verify your compiler version with gh aw --version and upgrade if needed: gh extension install github/gh-aw --pin <version>.
Quick Start
gh-aw workflows are authored as .md files with YAML frontmatter, compiled to .lock.yml via gh aw compile. The lock file is auto-generated — never edit it manually.
gh aw compile .github/workflows/<name>.md
Always commit the compiled lock file alongside the source .md.
CLI Commands
gh aw compile <name>
gh aw run <name>
gh aw run <name> --ref main
gh aw status
gh aw trial ./<name>.md --clone-repo owner/repo
gh aw audit <run-id>
gh aw upgrade
gh aw trial — Test workflows that aren't on main yet. Creates a temporary private repo, installs the workflow, and runs it. Essential for validating new workflows before merge, since workflow_dispatch requires the lock file on the default branch.
gh aw run --ref — Trigger a workflow on a specific branch. The workflow must already exist on that branch (registered by GitHub after the lock file is pushed).
🚨 Before You Build: Prefer Built-in gh-aw Features
CRITICAL RULE: Before implementing any trigger, output, scheduling, or interaction mechanism in a gh-aw workflow, check whether gh-aw has a built-in feature that does it. gh-aw extends GitHub Actions with many convenience features — manually reimplementing them is always worse (more code, more bugs, missing platform integration like emoji reactions, sanitized inputs, and noise reduction).
Step 1: Check the anti-patterns table below
Step 3: If a built-in exists, use it. If not, proceed with manual implementation.
Anti-Patterns: Manual Reimplementations to Avoid
| If you're about to implement... | Use this built-in instead | Docs |
|---|
issue_comment + startsWith(comment.body, '/cmd') | slash_command: trigger | Command Triggers |
| Manual emoji reaction on triggering comment | reaction: field under on: | Frontmatter |
| Posting "workflow started/completed" status comments | status-comment: true under on: | Frontmatter |
Fixed cron schedule (0 9 * * 1) for non-critical timing | schedule: weekly on monday around 9:00 (fuzzy) | Triggers |
Manual if: to skip bot-authored PRs | skip-bots: under on: | Triggers |
Manual if: to skip by author role | skip-roles: under on: | Triggers |
| Manual label check + removal for one-shot commands | label_command: trigger | Triggers |
| Editing old comments to collapse them | hide-older-comments: true on add-comment: | Safe Outputs |
| Creating no-op report issues | noop: report-as-issue: false | Safe Outputs / Monitoring |
| Auto-closing older issues from same workflow | close-older-issues: true on create-issue: | Safe Outputs |
| Disabling workflow after a date | stop-after: under on: | Triggers |
| Manual approval gating | manual-approval: under on: | Triggers |
Search-based skip logic in steps: | skip-if-match: / skip-if-no-match: under on: | Triggers |
| Locking issues to prevent concurrent edits | lock-for-agent: true under trigger | Triggers |
| Manually hiding agent comments | hide-comment: safe output | Safe Outputs |
| Custom post-processing jobs for agent output | safe-outputs.jobs: custom jobs with MCP tool access | Custom Safe Outputs |
| Wrapping GitHub Actions as agent-callable tools | safe-outputs.actions: action wrappers | Custom Safe Outputs |
| Triggering CI on agent-created PRs | github-token-for-extra-empty-commit: on create-pull-request | Triggering CI |
| No guard against agent approving PRs | allowed-events: [COMMENT] on submit-pull-request-review; or [COMMENT, REQUEST_CHANGES] with supersede-older-reviews: true to auto-dismiss stale blocking reviews | Safe Outputs |
Stale blocking reviews from previous /review runs | supersede-older-reviews: true on submit-pull-request-review — dismisses older same-workflow REQUEST_CHANGES reviews after posting replacement | Safe Outputs |
Merging PRs via shell gh pr merge in post-steps | merge-pull-request safe output (v0.70.0+) — executes in the safe-outputs job with proper permissions | Safe Outputs |
| Manually updating existing bot comments (delete + repost) | hide-older-comments: true on add-comment — collapses previous comments before posting new | Safe Outputs |
| Keeping PR branch up-to-date with base manually | update-branch: true on update-pull-request (v0.69.0+) — merges latest base into PR branch | Safe Outputs |
| Replying to inline review comments manually | reply-to-pull-request-review-comment safe output — threads replies under existing review comments | Safe Outputs |
| Resolving review threads manually | resolve-pull-request-review-thread safe output — marks review threads as resolved | Safe Outputs |
| Configuring the GitHub CLI proxy mode | tools.github.mode: gh-proxy (v0.70.0+) — official config; old cli-proxy feature flag is deprecated | Engines |
slash_command: without events: filter (subscribes to ALL comment events) | events: [pull_request_comment] or events: [issue_comment] | Command Triggers |
cancel-in-progress: true on slash_command: workflows | cancel-in-progress: false — non-matching events cancel in-progress agent runs | Concurrency |
Using pull_request trigger for agentic workflows | slash_command: or schedule — pull_request causes the "Approve and run" gate for ALL workflows | Triggers |
Note: gh-aw is actively developed. If a capability feels like something a framework would provide natively, check the reference docs — it probably exists even if it's not in this table yet.
For full architecture, security, fork handling, safe outputs, and troubleshooting details, see the official gh-aw docs.
Common Patterns
Pre-Agent Data Prep (the steps: pattern)
Use steps: for any operation requiring GitHub API access that the agent needs:
steps:
- name: Fetch PR data
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr view "$PR_NUMBER" --json title,body > pr-metadata.json
gh pr diff "$PR_NUMBER" --name-only > changed-files.txt
Payload Sanitization
Comment bodies, issue titles, and PR descriptions are user-controlled untrusted input. In pre-agent steps:, always use steps.<id>.outputs.text (sanitized) instead of raw ${{ github.event.comment.body }}. Within the agent job itself, unsanitized input is acceptable because the agent runs in a sandboxed container — but pair with tight safe-outputs:.
🛑 Recursive workflow triggering: Actions performed via GITHUB_TOKEN do NOT fire new workflow events (prevents infinite loops). Actions via GitHub App installation tokens or PATs DO fire events. This is why github-token-for-extra-empty-commit: requires a PAT — GITHUB_TOKEN pushes won't trigger CI on agent-created PRs.
Safe Outputs (Posting Comments)
safe-outputs:
add-comment:
max: 1
hide-older-comments: true
target: "*"
reply_to_id — add_comment supports a reply_to_id parameter to thread replies in discussion threads. On pull_request_review_comment triggers (v0.70.0+), add_comment automatically replies in the review thread instead of posting at the PR level.
Submit PR Reviews
safe-outputs:
submit-pull-request-review:
max: 1
allowed-events: [COMMENT]
footer: "if-body"
create-pull-request-review-comment:
max: 30
side: "RIGHT"
reply-to-pull-request-review-comment:
max: 10
resolve-pull-request-review-thread:
max: 10
Concurrency
Include all trigger-specific PR number sources. Use cancel-in-progress: false for slash_command: workflows — a non-matching event (ordinary comment) in the same concurrency group can cancel an in-progress matching run (the actual /command), killing the agent mid-execution:
concurrency:
group: "my-workflow-${{ github.event.issue.number || github.event.pull_request.number || inputs.pr_number || github.run_id }}"
cancel-in-progress: false
concurrency:
group: "my-workflow-${{ github.ref || github.run_id }}"
cancel-in-progress: true
⚠️ Pre-cancellation race: Cancellation is asynchronous — GitHub sends SIGTERM, waits up to 7500ms, then SIGKILL. Already-running steps may complete. An agent that already posted a comment cannot un-post it. A create-pull-request that already ran cannot un-create the PR. Concurrency is not a substitute for idempotency.
slash_command: Event Subscription
slash_command: compiles to broad event subscriptions — by default it listens to all comment-related events (issue open/edit, PR open/edit, every comment, every review comment, every discussion comment), then filters post-activation. This means:
- Runner cost: The pre-activation job runs on every matching event (~5-30s each), even when skipped. On busy repos this can be hundreds of skipped runs per day.
- Actions UI noise: Operators learn to ignore "skipped" runs and may miss real failures.
- Concurrency collisions: Non-matching events in the same concurrency group can cancel matching ones (see above).
Always narrow events: to the minimum needed:
on:
slash_command:
name: review
events: [pull_request_comment]
The "Approve and Run Workflows" Gate
The pull_request trigger causes an "Approve and run workflows" button for first-time fork contributors. This gate is dangerous, not protective:
- Alert fatigue — After clicking through dozens of legitimate first-time PRs, the click becomes muscle memory
- No per-workflow granularity — A single click approves ALL gated workflows, including any
pull_request_target workflows with full secrets
- No diff preview — The UI shows no preview of what will execute or which secrets are exposed
Design rule: Assume the approval gate will always be clicked. The only safe workflows are ones that produce the same outcome whether the actor is trusted or untrusted. Prefer issue_comment/slash_command: (not subject to the gate) or schedule/workflow_dispatch over pull_request when possible.
LabelOps
gh-aw provides label-based triggering patterns for both one-shot commands and persistent state tracking:
label_command: — One-shot command triggered by applying a label. The label is auto-removed after the workflow fires, making it self-resetting. Use for operations like "apply this label to trigger a review".
names: filtering — Filter label events to specific label names for persistent label-state awareness.
remove_label: false — Keep the label after triggering (for persistent state markers rather than one-shot commands).
See the LabelOps pattern guide for detailed examples and best practices.
Noise Reduction
Filter pull_request triggers to relevant paths and add a gate step:
on:
pull_request:
paths:
- 'src/**/tests/**'
steps:
- name: Gate — skip if no relevant files
if: github.event_name == 'pull_request'
run: |
FILES=$(gh pr diff "$PR_NUMBER" --name-only | grep -E '\.cs$' || true)
if [ -z "$FILES" ]; then exit 1; fi
Manual triggers (workflow_dispatch, issue_comment) should bypass the gate. Note: exit 1 causes a red ❌ on non-matching PRs — this is intentional (no built-in "skip" mechanism in gh-aw steps).
Fork PR Checkout (workflow_dispatch)
For workflow_dispatch workflows that need to evaluate a PR branch, implement a checkout step that: (1) verifies the PR author has write access and rejects fork PRs, (2) checks out the PR branch, and (3) restores .github/ and agent infrastructure from the base branch SHA — defense-in-depth even though the platform also does this restore automatically.
steps:
- name: Checkout PR and restore agent infrastructure
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr_number }}
run: |
# Verify PR author has write access, reject forks
AUTHOR=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login')
PERM=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$AUTHOR/permission" --jq '.permission')
if [[ "$PERM" != "admin" && "$PERM" != "write" && "$PERM" != "maintain" ]]; then
echo "::error::PR author $AUTHOR has $PERM access — requires write+"
exit 1
fi
gh pr checkout "$PR_NUMBER"
# Restore trusted .github/ from base branch
BASE_SHA=$(gh pr view "$PR_NUMBER" --json baseRefOid --jq '.baseRefOid')
git checkout "$BASE_SHA" -- .github/ .agents/ 2>/dev/null || true
For pull_request + fork support (not workflow_dispatch): add forks: ["*"] to the trigger frontmatter. The platform automatically preserves .github/ and .agents/ as a base-branch artifact in the activation job, then restores them after checkout_pr_branch.cjs — fork PRs cannot overwrite agent infrastructure (gh-aw#23769, resolved).
Operating Within a Fork
When you fork a repository, all workflow files come with it. Events inside your fork fire the workflows inside your fork, with your fork's secrets. This is separate from cross-fork PRs and is frequently a surprise.
Guard pattern — prevent workflows from running in forks (with a manual escape hatch):
jobs:
guard:
if: github.event_name == 'workflow_dispatch' || !github.event.repository.fork
⚠️ YAML gotcha: Don't start a bare if: value with ! — it's a YAML tag indicator. Either wrap in ${{ }} or use parentheses: if: (!github.event.repository.fork).
Security-Critical Patterns
These patterns are the most commonly missed when building secure workflows. Use all where applicable.
1. Role-based access control — roles: controls who can trigger the workflow. Without it, any user (including the PR author) can trigger /review on a malicious PR designed to prompt-inject the reviewer. The default [admin, maintainer, write] is injected automatically for workflows with "unsafe" events (issues, comments, PRs, discussions):
on:
slash_command:
name: review
events: [pull_request_comment]
roles: [admin, maintainer, write]
⚠️ triage role footgun: triage is excluded from the default allowlist. A label_command: workflow (which requires triage to apply the label) will fire but the activation job will deny a triage user unless roles: is broadened. Add triage explicitly when triage users are the primary operators.
2. Prevent accidental PR approvals — always restrict review workflows; otherwise the agent can approve PRs and bypass branch protection rules (gh-aw#25439):
safe-outputs:
submit-pull-request-review:
allowed-events: [COMMENT]
supersede-older-reviews: true — When using REQUEST_CHANGES, set this to automatically dismiss older blocking reviews from the same workflow after posting a replacement. This solves the stale-review problem: without it, a REQUEST_CHANGES review persists even after the author fixes everything and re-runs /review, because gh-aw has no dismiss-pull-request-review safe output and the compiler rejects pull-requests: write. With supersede-older-reviews, the new review replaces the old one (best-effort). This makes [COMMENT, REQUEST_CHANGES] a viable option alongside [COMMENT]-only.
3. Integrity filtering — controls what content the agent can see (vs. roles: which controls who can trigger). The MCP gateway intercepts GitHub tool calls and filters content by author trust level before the AI engine sees it. Filtered items are logged as DIFC_FILTERED events — inspect them with gh aw logs --filtered-integrity.
| Level | Who qualifies |
|---|
merged | Merged PRs; commits on default branch (any author) |
approved | OWNER, MEMBER, COLLABORATOR; non-fork PRs on public repos; all items in private repos; platform bots; trusted-users |
unapproved | CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR |
none | All content including FIRST_TIMER and users with no association |
blocked | Users in blocked-users — always denied, cannot be promoted |
Defaults: Public repos default to min-integrity: approved when unconfigured. Private repos default to min-integrity: none.
tools:
github:
min-integrity: approved
allowed-repos: "myorg/*"
toolsets: [pull_requests, repos]
trusted-users: [contractor-1]
blocked-users: [spam-bot]
approval-labels: [human-reviewed]
integrity-proxy: true
allowed-repos scoping: Restricts which repositories' content the agent can see. Accepts "all" (default), "public", or an array of patterns: ["myorg/*", "partner/repo"]. When specified alongside min-integrity, both fields must be present.
approval-labels: Label names that promote an issue or PR to approved integrity regardless of author association. Useful as a human-review gate: a maintainer applies the label, and the agent can then see and act on it. Accepts a static list or a GitHub Actions expression.
trusted-users / blocked-users / approval-labels — GitHub Actions expressions: All three fields accept GitHub Actions expressions (e.g., ${{ vars.TRUSTED_CONTRACTORS }}) in addition to static arrays. This enables centralized management via repository variables or environment variables (GH_AW_GITHUB_*).
Centralized management via GH_AW_GITHUB_* environment variables: Organization-wide integrity settings can be distributed via Actions environment variables prefixed with GH_AW_GITHUB_ (e.g., GH_AW_GITHUB_TRUSTED_USERS). These are merged with per-workflow frontmatter.
integrity-proxy: false: Disables the DIFC proxy for pre-agent gh CLI calls in workflow steps. Only use when you deliberately want to bypass integrity checks for a non-agentic step. Does not affect the MCP gateway filtering.
Effective integrity computation order: An item's final integrity level is determined by (highest wins): blocked-users (forced deny) → trusted-users (forced approved) → approval-labels (promote to approved) → endorsement/disapproval reactions (see below) → author association default.
Reaction-based integrity (v0.68.2+, features.integrity-reactions: true): Reactions on issues/PRs/comments can dynamically promote or demote integrity:
features:
integrity-reactions: true
tools:
github:
min-integrity: approved
endorsement-reactions: [THUMBS_UP, HEART]
disapproval-reactions: [THUMBS_DOWN, CONFUSED]
endorser-min-integrity: approved
disapproval-integrity: none
Interaction with roles::
roles: | min-integrity | Effect |
|---|
Default [admin, maintainer, write] | approved | Most restrictive. Only trusted actors trigger; agent sees only trusted content |
| Default | unapproved/none | Trusted actors only, but agent reads community content. Good for post-merge scans |
all | approved | Two-layer defense. Any actor triggers, but agent only sees trusted content |
all | none | Widest exposure. Must pair with minimal safe-outputs — only remaining constraint |
⚠️ Compiler bug (v0.62.2): Hardcoded min-integrity in source emits an incomplete guard policy (missing repos field) that crashes the MCP Gateway. Rely on the automatic determine-automatic-lockdown step instead, which applies approved for public repos by default. This may be fixed in newer compiler versions — test before hardcoding.
4. CI triggering + protected file safety for agent-created PRs — GITHUB_TOKEN pushes don't trigger CI; a PAT/App token is required. protected-files controls what happens when the agent modifies package manifests or .github/:
safe-outputs:
create-pull-request:
github-token-for-extra-empty-commit: ${{ secrets.PAT_OR_APP_TOKEN }}
protected-files: fallback-to-issue
5. Fork PR checkout for workflow_dispatch — the platform's checkout_pr_branch.cjs is skipped for workflow_dispatch, so you must implement a checkout step that verifies write access, rejects fork PRs, and restores trusted .github/ from the base branch. See the Fork PR Checkout pattern above for a complete example.
6. XPIA hardening (v0.70.0+) — Cross-prompt injection (XPIA) sanitization paths have been hardened. disable-xpia-prompt is now rejected at compile time in strict mode — do not use it. If a workflow previously relied on it, remove the flag; the runtime handles XPIA protection by default.
Idempotency and the Edited-Comment Time-Bomb
Slash command workflows MUST be idempotent. Treat every activation as if the same command might already be running for the same target. Check before acting, claim a lock, no-op if already in progress or done.
gh-aw provides lock-for-agent: true to automatically lock/unlock the issue during execution, but use with caution — it prevents genuine users from interacting on the issue/PR while the workflow runs.
State tracking for scheduled pollers: Use comment-based state to track what's been processed. Edit a comment's visible markdown to reflect status (⏳ in progress / ✅ done), and append invisible <!-- state-machine --> HTML comments as an append-only audit trail. This gives human-readable status and machine-parseable history in one artifact.
🛑 The edited-comment time-bomb: An attacker can edit a 6-month-old comment on a closed issue or PR, injecting /command or any payload — issue_comment.edited fires TODAY against today's secrets, today's permissions:, today's safe-outputs:. The workflow has no concept of "this comment was created when our security model was different." For raw issue_comment, use types: [created] — add edited only if you've designed for this attack vector.
Read-Only Contributor Write Surface
What the agent can do is determined by permissions: and safe-outputs: — NOT by the actor who fired it. When a workflow accepts a read-only contributor as the trigger (roles: all), that contributor effectively gets bot-level write access to anything the workflow grants the agent.
What a read-only user can fire:
| Action | Can fire? |
|---|
| Open an issue, comment, react with emoji | ✅ |
/slash-command in any comment/body they author | ✅ |
| Open a PR (from fork) | ✅ |
| Apply a label | ❌ (requires triage) |
Invoke workflow_dispatch | ❌ (requires write) |
| Click "Approve and run workflows" | ❌ (requires write) |
Defenses, in priority order:
- Leave
roles: at its default [admin, maintainer, write]
- Minimize
permissions: to the smallest set the agent needs
- Minimize
safe-outputs: to only the mutations the workflow needs
- For PR-touching workflows: never check out the PR head SHA in a job that has secrets
- Add an explicit fork guard:
if: github.event.pull_request.head.repo.fork == false
- Configure
min-integrity to control what content the agent can see
Trigger Selection Guide
Choose the right trigger for your workflow. Triggers are grouped by recommended usage level.
✅ Recommended
| Trigger | Best for | Key advantage |
|---|
workflow_dispatch | Manual escape hatch, debugging, ad-hoc runs | Write+ required; auto-paired with most triggers. ⚠️ Branch selection is user-controlled — a write user can dispatch against a stale branch with weaker permissions:, different safe-outputs:, or a friendlier prompt |
schedule | Periodic housekeeping, polling-based PR operations | Best concurrency story; no event spamming; no approval gate |
labeled / label_command: | Human-in-the-loop gate via label application | Triage+ required to apply label; one-shot with auto-remove. Set min-integrity: none — the label application IS the human gate, replacing integrity filtering |
issues | Community-facing issue workflows | Immediate; roles: all acceptable with tight safe-outputs |
release / milestone | Post-release/milestone automation | Trusted trigger (write+) |
⚠️ Use with Caution
| Trigger | Headline risk |
|---|
push | Always use explicit branches: — bare on: push fires on every branch including bot/dependency/codeflow branches. The trigger most likely to turn a PoC into a billing surprise. Rapid pushes (rebasing, force-pushing) stack runs unless cancel-in-progress: true |
issue_comment / slash_command: | Broad underlying subscription; concurrency catastrophe; edited-comment time-bomb |
pull_request_review | Fires for ALL review types including COMMENT from any user, not just approvals |
discussion / discussion_comment | Most-open untrusted-input surface; no approval gate; lower visibility than issues |
pull_request.synchronize Gotchas
synchronize fires once per push to a PR branch (not per commit). Things that do NOT fire synchronize:
- Draft → ready-for-review: Fires
ready_for_review, not synchronize. Workflows using default types: [opened, synchronize, reopened] won't re-run CI when a draft is marked ready
- Base-ref edits: Changing the PR's base branch fires
edited (with changes.base), not synchronize
- Pushes to the base branch: Someone merging to
main while your PR targets main does NOT fire synchronize on your PR — it fires push on main. Your CI won't re-run against the new base unless you push to your branch
- Approval dismissal: Branch protection's "Dismiss stale approvals on new commits" fires on the same head-SHA-changed event. A force-push that doesn't change file contents (rebase onto current
main) still invalidates all prior approvals
⛔ Avoid
| Trigger | Why |
|---|
pull_request | Causes "Approve and run" gate for ALL workflows; clicking approves everything including pull_request_target with full secrets. Prefer slash_command:, schedule, or label_command: |
pull_request_target | Runs on base ref with full secrets and write token — most exploited vulnerability class. Never check out PR head SHA |
workflow_run | pull_request_target's quieter sibling — launders untrusted fork artifacts into privileged context with no approval gate. Classic pwn: sandboxed pull_request workflow uploads artifacts (e.g., coverage.json), then workflow_run downloads and acts on them with full secrets. Artifact may contain shell-injection or prompt-injection payloads. No UI signal connects the upstream PR to the downstream run. Treat all downloaded artifacts as untrusted |
Design Principles
- Deterministic by default. Use deterministic Actions and reusable workflows; agentic workflows only when the input is unstructured or AI unlocks a capability deterministic code cannot provide.
- Limitations ARE the security model. Don't engineer bypasses (
pull_request_target for write access, PAT pools to evade bot attribution, workflow_run to escape approval gates, roles: all to widen the actor pool). When a boundary blocks a legitimate goal, escalate to platform owners.
- Limit the agent job to agent-suitable work. Keep filtering/skipping in pre-agent steps. Execute deterministic scripts before and after the agent job.
- Apply least privilege on every dimension. Minimum
permissions:, safe-outputs:, network.allowed:, secrets, tools:. The agent sandbox makes untrusted input safe to process inside the agent; the same operation in pre/post-agent steps runs on the runner host with full secret access.
- Mind the signal-to-noise ratio. Convenience triggers compile to broad subscriptions. Every event spawns a workflow run consuming a runner slot. The activation step must be cheap, and the worst-case invocation rate must be estimated and acceptable.
- Understand the
GITHUB_TOKEN recursion boundary. Actions via GITHUB_TOKEN do NOT fire new workflow events (prevents infinite loops). GitHub App tokens and PATs DO fire events. This is by design — it's why github-token-for-extra-empty-commit: needs a PAT, and why bot comments via GITHUB_TOKEN don't trigger issue_comment workflows.
Frontmatter Features
source: "githubnext/agentics/workflows/ci-doctor.md@v1.0.0"
redirect: "owner/repo/new-name.md@main"
private: true
resources:
- triage-issue.md
- shared/helper-action.yml
labels: ["automation", "ci"]
checkout: false
engine:
id: copilot
model: claude-sonnet-4.6
bare: true
pre-agent-steps:
- name: Install dependencies
run: npm install
sandbox:
agent:
version: "0.25.28"
runtimes:
dotnet:
version: "9.0"
node:
version: "22"
imports:
- uses: shared/apm.md
with:
packages:
- microsoft/apm-sample-package
on:
needs: pre_activation
pre-agent-steps: (v0.68.5+) — Inject custom steps immediately before the agent engine runs. Runs after checkout and .github/ restore, but before the agent starts. Use for data preparation, dependency installation, or environment setup that needs the checked-out code. Supports imports and merge semantics.
engine.bare: true (v0.68.6+) — Skip loading AGENTS.md context. Ideal for non-code workflows (triage, reporting, ops) where repository code context is irrelevant.
sandbox.agent.version (v0.69.1+) — Pin the AWF sandbox version for reproducibility. Useful for staged rollouts.
redirect: (v0.69.2+) — Specifies the new canonical location when a workflow is moved or renamed. gh aw update follows redirect chains automatically.
comment_memory safe output (v0.69.2+) — Agents can persist structured memory in a managed issue/PR comment. Memory files are materialized under /tmp/gh-aw/comment-memory/ before the agent runs and synced back after. Enables stateful agents across runs without external storage.
on.needs: (v0.70.0+) — Express dependencies on custom pre_activation/activation jobs, enabling GitHub App credentials to be sourced from upstream job outputs. See also safe-outputs.needs (v0.69.1+) for credential-supply dependencies in the safe-outputs job.
merge-pull-request safe output (v0.70.0+) — Merge a PR directly as a safe output. Executes in the safe-outputs job with proper write permissions, not inside the agent container.
tools.github.mode: gh-proxy (v0.70.0+) — Configure the GitHub CLI proxy feature. The deprecated cli-proxy feature flag is scheduled for removal; migrate to this form:
tools:
github:
mode: gh-proxy
Claude engine (v0.71.0+) — The Claude engine has two permission modes: acceptEdits (default — agent proposes edits that the safe-outputs layer validates) and bypassPermissions (activated when unrestricted bash bash: "*" is granted — agent executes directly). v0.71.0 updated the internal wiring; recompile Claude-engine workflows compiled with older versions.
checkout: false — Skip the default repository checkout when the workflow doesn't need source code (e.g., ChatOps commands that only call APIs via web-fetch). Saves ~10-30s of runner time.
engine.max-turns — Limit the number of turns the agent can take. Set in the engine block: engine: { id: copilot, max-turns: 15 }. Preserved through shared imports (fixed in v0.68.3).
Available engines: copilot (default), claude, codex, crush (v0.69.0+, replaces OpenCode), gemini. See Engines reference.
Available tools: web-fetch (fetch URLs), bash (shell commands), GitHub MCP toolsets (pull_requests, repos, issues, etc.). Use tools: [web-fetch] for workflows that call external APIs.
MCP config location: .github/mcp.json (v0.68.5+ — previously .mcp.json at repo root). Migrate existing configs manually.
vulnerability-alerts permission (v0.69.3+) — Available as a GITHUB_TOKEN permission scope for workflows that need to read security alerts.
Breaking Changes
network.firewall removed (v0.69.2) — This deprecated frontmatter key is now rejected by the compiler. Migrate with: gh aw fix --write.
Supported runtimes: node, python, go, uv, bun, deno, ruby, java, dotnet, elixir.
Further Reading
For deep-dive details on execution model, security boundaries, fork handling, safe output types, and known issues, see references/architecture.md.
See also the official gh-aw documentation for:
- Triggers — complete trigger reference with activity types
- Frontmatter — all configuration options
- Safe outputs — complete list of 30+ types, key options for each
- Integrity filtering — content trust hierarchy and configuration