Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ee76d99
Add GitHub Actions workflow to run evaluate-pr-tests via Copilot CLI
github-actions[bot] Mar 18, 2026
8fa896d
Security: eliminate shell injection by moving comment.body to job-lev…
github-actions[bot] Mar 18, 2026
f26b820
Add self-trigger paths and token check for PR testing
github-actions[bot] Mar 18, 2026
54be271
Fix artifact download path for comment posting
github-actions[bot] Mar 18, 2026
8cf0fd6
Add GH_TOKEN for PR resolution and dispatch usage docs
github-actions[bot] Mar 18, 2026
daf4172
Pre-fetch PR context in gate job, checkout workflow branch for skills
github-actions[bot] Mar 18, 2026
1476c04
Pre-run Gather-TestContext.ps1 in gate job, fix prompt
github-actions[bot] Mar 18, 2026
5c9c95e
Fix: checkout PR code to workspace-relative path
github-actions[bot] Mar 18, 2026
b167cdd
Use MAUI_BOT_TOKEN for PR comments
kubaflo Mar 18, 2026
25f5917
Update Post-CopilotComment.ps1
kubaflo Mar 18, 2026
b26b06c
Update Post-CopilotComment.ps1
kubaflo Mar 18, 2026
d8b7326
Replace CLI workflow with gh-aw for evaluate-pr-tests
github-actions[bot] Mar 19, 2026
7d453f2
Merge branch 'feature/copilot-evaluate-tests-workflow' of https://git…
github-actions[bot] Mar 19, 2026
9ff61ac
Fix: remove github.event_name from prompt (not in safe expression list)
github-actions[bot] Mar 19, 2026
9101aab
Recompile lock.yml with gh-aw v0.62.2
github-actions[bot] Mar 19, 2026
c0bc946
Add workflow_dispatch input for evaluating any PR
github-actions[bot] Mar 19, 2026
e5d3b66
Fix: add-comment target '*' for workflow_dispatch support
github-actions[bot] Mar 19, 2026
80e3e83
Wrap evaluation comment in collapsible details block
github-actions[bot] Mar 20, 2026
c898509
Simplify prompt: delegate to skill, add gh CLI for fork PRs
github-actions[bot] Mar 20, 2026
7786352
Remove pull_requests MCP toolset (use gh CLI instead)
github-actions[bot] Mar 20, 2026
5bcb1a3
Fix: checkout PR branch before analysis, add -PrNumber to script
github-actions[bot] Mar 20, 2026
32e6200
Fix: use API-based PR access instead of gh pr checkout
github-actions[bot] Mar 20, 2026
2eef97c
Fix: use pre-agent steps to checkout PR branch
github-actions[bot] Mar 20, 2026
2258343
Add thumbs up/down feedback prompt to evaluation comments
github-actions[bot] Mar 23, 2026
1dd4dd8
Address security review findings from 5-model consensus
github-actions[bot] Mar 23, 2026
03c3193
Reduce noise: widen test paths, add test-file gate
github-actions[bot] Mar 23, 2026
e65c602
Address re-review: fail-closed fork guard, binary-safe file download
github-actions[bot] Mar 23, 2026
7b06ea0
Fix fork PR evaluation: fetch changed files via API instead of skippi…
github-actions[bot] Mar 23, 2026
acf8b1c
Remove fork guard, allow checkout for all PRs, add gh-aw instructions
github-actions[bot] Mar 23, 2026
8ab12f4
Restore skill/instruction files after PR checkout
github-actions[bot] Mar 23, 2026
77361d5
Address re-review #3: restore ordering, copilot-instructions, rm -rf
github-actions[bot] Mar 24, 2026
39405a9
Update fork hard-fail message with workflow_dispatch workaround
github-actions[bot] Mar 24, 2026
18853ef
Make fork guard fail-closed on API errors
github-actions[bot] Mar 24, 2026
62d9d02
Upgrade agent model to claude-sonnet-4.6
github-actions[bot] Mar 24, 2026
efde5ef
Simplify: remove untracked backup and agent restore prompt
github-actions[bot] Mar 24, 2026
1d39930
Update gh-aw instructions with execution model, fork handling, and fa…
github-actions[bot] Mar 24, 2026
207c3dd
Extract shared gh-aw PR checkout script for reuse across workflows
github-actions[bot] Mar 24, 2026
876587d
Convert gh-aw checkout script to PowerShell for consistency
github-actions[bot] Mar 24, 2026
de1d9e1
Replace fork hard-fail with optimistic path and agent-side skill check
github-actions[bot] Mar 24, 2026
f04321d
Address security review: remove dead code, update stale docs, improve…
github-actions[bot] Mar 24, 2026
54b76cb
Security: add fail-closed fork guard, escape filenames in context report
github-actions[bot] Mar 24, 2026
f94663b
Skip draft PRs, add ready_for_review trigger, reorder concurrency group
github-actions[bot] Mar 25, 2026
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
10 changes: 10 additions & 0 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@
"version": "v8",
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
},
"github/gh-aw-actions/setup@v0.62.1": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.62.1",
"sha": "95c4e2aa6adbdf63ff0b0fbf09945ad4f4716fea"
},
"github/gh-aw-actions/setup@v0.62.2": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.62.2",
"sha": "20045bbd5ad2632b9809856c389708eab1bd16ef"
},
"github/gh-aw/actions/setup@v0.43.19": {
"repo": "github/gh-aw/actions/setup",
"version": "v0.43.19",
Expand Down
223 changes: 223 additions & 0 deletions .github/instructions/gh-aw-workflows.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
applyTo:
- ".github/workflows/*.md"
- ".github/workflows/*.lock.yml"
---

# gh-aw (GitHub Agentic Workflows) Guidelines

## Architecture

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

### Execution Model

```
activation job (renders prompt from base branch .md via runtime-import)
agent job:
user steps: (pre-agent, OUTSIDE firewall, has GITHUB_TOKEN)
platform steps: (configure git → checkout_pr_branch.cjs → install CLI)
agent: (INSIDE sandboxed container, NO credentials)
```

| Context | Has GITHUB_TOKEN | Has gh CLI | Has git creds | Can execute scripts |
|---------|-----------------|-----------|---------------|-------------------|
| `steps:` (user) | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes — **be careful** |
| Platform steps | ✅ Yes | ✅ Yes | ✅ Yes | Platform-controlled |
| Agent container | ❌ Scrubbed | ❌ Scrubbed | ❌ Scrubbed | ✅ But sandboxed |

### Step Ordering (Critical)

User `steps:` **always run before** platform-generated steps. You cannot insert user steps after platform steps.

The platform's `checkout_pr_branch.cjs` runs with `if: (github.event.pull_request) || (github.event.issue.pull_request)` — it is **skipped** for `workflow_dispatch` triggers.

### Prompt Rendering

The prompt is built in the **activation job** via `{{#runtime-import .github/workflows/<name>.md}}`. This reads the `.md` file from the **base branch** workspace (before any PR checkout). The rendered prompt is uploaded as an artifact and downloaded by the agent job.

- The agent prompt is always the base branch version — fork PRs cannot alter it
- The prompt references files on disk (e.g., `SKILL.md`) — those files must exist in the agent's workspace

### Fork PR Activation Gate

`gh aw compile` automatically injects a fork guard into the activation job's `if:` condition: `head.repo.id == repository_id`. This blocks fork PRs on `pull_request` events. This is **platform behavior** — do not add it manually.

## Fork PR Handling

### The "pwn-request" Threat Model

The classic attack requires **checkout + execution** of fork code with elevated credentials. Checkout alone is not dangerous — the vulnerability is executing workspace scripts with `GITHUB_TOKEN`.

Reference: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/

### Fork PR Behavior by Trigger

| Trigger | `checkout_pr_branch.cjs` runs? | Fork handling |
|---------|-------------------------------|---------------|
| `pull_request` | ✅ Yes | Blocked by auto-generated activation gate |
| `workflow_dispatch` | ❌ Skipped | ✅ Works — user steps handle checkout and restore is final |
| `issue_comment` (same-repo) | ✅ Yes | ✅ Works — files already on PR branch |
| `issue_comment` (fork) | N/A | ❌ Blocked by fail-closed fork guard in `Checkout-GhAwPr.ps1` |

### The `issue_comment` + Fork Problem

For `/slash-command` triggers on fork PRs, `checkout_pr_branch.cjs` runs AFTER all user steps and re-checks out the fork branch. This overwrites any files restored by user steps (e.g., `.github/skills/`). There is no way to run user steps after platform steps. A fork could include a crafted `SKILL.md` that alters the agent's evaluation behavior.

**Current approach (fail-closed fork guard):** `Checkout-GhAwPr.ps1` checks `isCrossRepository` via `gh pr view` for `issue_comment` triggers. If the PR is from a fork or the API call fails, the script exits with code 1. Fork PRs should use `workflow_dispatch` instead, where `checkout_pr_branch.cjs` is skipped and the user step restore is the final workspace state.

**Upstream issue:** [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — "Using gh-aw in forks of repositories"

### Safe Pattern: Checkout + Restore

Use the shared `.github/scripts/Checkout-GhAwPr.ps1` script, which implements checkout + restore in a single reusable step:

```yaml
steps:
- name: Checkout PR and restore agent infrastructure
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
run: pwsh .github/scripts/Checkout-GhAwPr.ps1
```

The script:
1. Captures the base branch SHA before checkout
2. **Fork guard** (`issue_comment` only): checks `isCrossRepository` — exits 1 if fork or API failure
3. Checks out the PR branch via `gh pr checkout`
4. Deletes `.github/skills/` and `.github/instructions/` (prevents fork-added files)
5. Restores them from the base branch SHA (best-effort, non-fatal)

**Behavior by trigger:**
- **`workflow_dispatch`**: Fork guard skipped. Platform checkout is skipped, so the restore IS the final workspace state (trusted files from base branch)
- **`issue_comment`** (same-repo): Fork guard passes. Platform re-checks out PR branch — files already match, effectively a no-op
- **`issue_comment`** (fork): Fork guard rejects — exits 1 with actionable notice to use `workflow_dispatch`
- **`pull_request`** (same-repo): Fork guard skipped. Files already exist, restore is a no-op

### Anti-Patterns

**Do NOT skip checkout for fork PRs:**

```bash
# ❌ ANTI-PATTERN: Makes fork PRs unevaluable
if [ "$HEAD_OWNER" != "$BASE_OWNER" ]; then
echo "Skipping checkout for fork PR"
exit 0 # Agent evaluates workflow branch instead of PR
fi
```

Skipping checkout means the agent evaluates the wrong files. The correct approach is: always check out the PR, then restore agent infrastructure from the base branch.

**Do NOT execute workspace code after fork checkout:**

```yaml
# ❌ DANGEROUS: runs fork code with GITHUB_TOKEN
- name: Checkout PR
run: gh pr checkout "$PR_NUMBER" ...
- name: Run analysis
run: pwsh .github/skills/some-script.ps1
```

If you need to run scripts, either:
1. Run them **before** the checkout (from the base branch)
2. Run them **inside the agent container** (sandboxed, no tokens)

## Compilation

```bash
# Compile after every change to the .md source
gh aw compile .github/workflows/<name>.md

# This updates:
# - .github/workflows/<name>.lock.yml (auto-generated)
# - .github/aw/actions-lock.json
```

**Always commit the compiled lock file alongside the source `.md`.**

## Common Patterns

### Pre-Agent Data Prep (the `steps:` pattern)

Use `steps:` for any operation requiring GitHub API access that the agent needs:

```yaml
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
```

### Safe Outputs (Posting Comments)

```yaml
safe-outputs:
add-comment:
max: 1
target: "*" # Required for workflow_dispatch (no triggering PR context)
```

### Concurrency

Include all trigger-specific PR number sources:

```yaml
concurrency:
group: "my-workflow-${{ github.event.issue.number || github.event.pull_request.number || inputs.pr_number || github.run_id }}"
cancel-in-progress: true
```

### Noise Reduction

Filter `pull_request` triggers to relevant paths and add a gate step:

```yaml
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).

## Limitations

| What | Behavior | Workaround |
|------|----------|------------|
| User steps always before platform steps | Cannot run user code after `checkout_pr_branch.cjs` | Use `workflow_dispatch` for fork PRs; see [gh-aw#18481](https://github.com/github/gh-aw/issues/18481) |
| `--allow-all-tools` in lock.yml | Emitted by `gh aw compile` | Cannot override from `.md` source |
| MCP integrity filtering | Fork PRs blocked as "unapproved" | Use `steps:` checkout instead of MCP |
| `gh` CLI inside agent | Credentials scrubbed | Use `steps:` for API calls, or MCP tools |
| `issue_comment` trigger | Requires workflow on default branch | Must merge to `main` before `/slash-commands` work |
| Duplicate runs | gh-aw sometimes creates 2 runs per dispatch | Harmless, use concurrency groups |

### Upstream References

- [github/gh-aw#18481](https://github.com/github/gh-aw/issues/18481) — Fork support tracking issue
- [github/gh-aw#18518](https://github.com/github/gh-aw/issues/18518) — Fork detection in `gh aw init`
- [github/gh-aw#18521](https://github.com/github/gh-aw/issues/18521) — Fork support documentation

## Troubleshooting

| Symptom | Cause | Fix |
|---------|-------|-----|
| Agent evaluates wrong PR | `workflow_dispatch` checks out workflow branch | Add `gh pr checkout` in `steps:` |
| Agent can't find SKILL.md | Fork PR branch doesn't have `.github/skills/` | Agent posts "rebase or use `workflow_dispatch`" message; or rebase fork on `main` |
| Fork PR rejected on `/evaluate-tests` | Fail-closed fork guard in `Checkout-GhAwPr.ps1` | Use `workflow_dispatch` with `pr_number` input instead |
| `gh` commands fail in agent | Credentials scrubbed inside container | Move to `steps:` section |
| Lock file out of date | Forgot to recompile | Run `gh aw compile` |
| Integrity filtering warning | MCP reading fork PR data | Expected, non-blocking |
| `/slash-command` doesn't trigger | Workflow not on default branch | Merge to `main` first |
100 changes: 100 additions & 0 deletions .github/scripts/Checkout-GhAwPr.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<#
.SYNOPSIS
Shared PR checkout for gh-aw (GitHub Agentic Workflows).

.DESCRIPTION
Checks out a PR branch and restores trusted agent infrastructure (skills,
instructions) from the base branch. For issue_comment triggers, fork PRs
are rejected (fail-closed) because the platform's checkout_pr_branch.cjs
overwrites restored files after user steps. Fork PRs should use
workflow_dispatch instead.

SECURITY NOTE: This script checks out PR code onto disk. This is safe
because NO subsequent user steps execute workspace code — the gh-aw
platform copies the workspace into a sandboxed container with scrubbed
credentials before starting the agent. The classic "pwn-request" attack
requires checkout + execution; we only do checkout.

DO NOT add steps after this that run scripts from the workspace
(e.g., ./build.sh, pwsh ./script.ps1). That would create an actual
fork code execution vulnerability. See:
https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/

.NOTES
Required environment variables (set by the calling workflow step):
GH_TOKEN - GitHub token for API access
PR_NUMBER - PR number to check out
GITHUB_REPOSITORY - owner/repo (set by GitHub Actions)
GITHUB_ENV - path to env file (set by GitHub Actions)
GITHUB_EVENT_NAME - trigger type (set by GitHub Actions)
#>

$ErrorActionPreference = 'Stop'

# ── Validate inputs ──────────────────────────────────────────────────────────

if (-not $env:PR_NUMBER -or $env:PR_NUMBER -eq '0') {
Write-Host "No PR number available, using default checkout"
exit 0
}

$PrNumber = $env:PR_NUMBER

# ── Fork guard (issue_comment only) ─────────────────────────────────────────
# For issue_comment triggers, platform's checkout_pr_branch.cjs runs AFTER user
# steps and re-checks out the fork branch, overwriting any restored skill/instruction
# files. A fork could include a crafted SKILL.md that alters agent behavior.
# Fail closed: if we can't verify origin, exit 1 (not 0).
# Fork PRs can still be evaluated via workflow_dispatch (where platform checkout is skipped).

if ($env:GITHUB_EVENT_NAME -eq 'issue_comment') {
$isFork = gh pr view $PrNumber --repo $env:GITHUB_REPOSITORY --json isCrossRepository --jq '.isCrossRepository' 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Could not verify PR origin — failing closed"
exit 1
}
if ($isFork -eq 'true') {
Write-Host "::notice::Fork PR detected — /evaluate-tests via issue_comment is not supported for fork PRs. Use workflow_dispatch with pr_number=$PrNumber instead."
exit 1
}
}

# ── Save base branch SHA ─────────────────────────────────────────────────────
# Must be captured BEFORE checkout replaces HEAD.
# Exported for potential use by downstream platform steps (e.g., checkout_pr_branch.cjs)

$BaseSha = git rev-parse HEAD
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Failed to get current HEAD SHA"
exit 1
}
Add-Content -Path $env:GITHUB_ENV -Value "BASE_SHA=$BaseSha"

# ── Checkout PR branch ──────────────────────────────────────────────────────

Write-Host "Checking out PR #$PrNumber..."
gh pr checkout $PrNumber --repo $env:GITHUB_REPOSITORY
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Failed to checkout PR #$PrNumber"
exit 1
}
Write-Host "✅ Checked out PR #$PrNumber"
git log --oneline -1

# ── Restore agent infrastructure from base branch ────────────────────────────
# Best-effort restore of skill/instruction files from the base branch.
# - workflow_dispatch: platform checkout is skipped, so this IS the final state
# - issue_comment (same-repo): platform's checkout_pr_branch.cjs runs after and
# overwrites, but files already match (same repo). Fork PRs are blocked above.
# - pull_request (same-repo): files already exist, this is a no-op
# rm -rf first to prevent fork-added files from surviving the restore.

if (Test-Path '.github/skills/') { Remove-Item -Recurse -Force '.github/skills/' }
if (Test-Path '.github/instructions/') { Remove-Item -Recurse -Force '.github/instructions/' }

git checkout $BaseSha -- .github/skills/ .github/instructions/ .github/copilot-instructions.md 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "✅ Restored agent infrastructure from base branch ($BaseSha)"
} else {
Write-Host "⚠️ Could not restore agent infrastructure from base branch — files may come from the PR branch"
}
Loading
Loading