Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@
"version": "v9.0.0",
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
},
"github/gh-aw-actions/setup-cli@v0.79.8": {
"repo": "github/gh-aw-actions/setup-cli",
"version": "v0.79.8",
"sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47"
},
"github/gh-aw-actions/setup@v0.79.8": {
"github/gh-aw-actions/setup@v0.80.9": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.79.8",
"sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47"
"version": "v0.80.9",
"sha": "8c7d04ebf1ece56cd381446125da3e0f6896294a"
}
}
}
433 changes: 277 additions & 156 deletions .github/workflows/android-reviewer.lock.yml

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion .github/workflows/android-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,40 @@ on:
name: review
events: [pull_request_comment]
roles: [admin, maintainer, write]
environment: copilot-pr-reviewer
# ###############################################################
# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN.
# Run agentic jobs in an isolated `copilot-pat-pool` environment.
#
# When org-level billing is available, this will be removed.
# See `shared/pat_pool.README.md` for more information.
# ###############################################################
imports:
- uses: shared/pat_pool.md
with:
environment: copilot-pat-pool

environment: copilot-pat-pool
permissions:
contents: read
pull-requests: read
engine:
id: copilot
model: claude-opus-4.8
env:
COPILOT_GITHUB_TOKEN: |
${{ case(
needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0,
needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1,
needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2,
needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3,
needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4,
needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5,
needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6,
needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7,
needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8,
needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9,
'NO COPILOT PAT AVAILABLE')
}}
max-daily-ai-credits: -1
max-ai-credits: -1
network:
Expand Down
433 changes: 276 additions & 157 deletions .github/workflows/nightly-fix-finder.lock.yml

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion .github/workflows/nightly-fix-finder.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ on:
permissions:
contents: read
issues: read
environment: copilot-pr-reviewer
# ###############################################################
# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN.
# Run agentic jobs in an isolated `copilot-pat-pool` environment.
#
# When org-level billing is available, this will be removed.
# See `shared/pat_pool.README.md` for more information.
# ###############################################################
imports:
- uses: shared/pat_pool.md
with:
environment: copilot-pat-pool

environment: copilot-pat-pool
network:
allowed:
- defaults
Expand Down Expand Up @@ -79,6 +91,21 @@ description: Nightly scan for random code improvement opportunities, files issue
engine:
id: copilot
model: claude-opus-4.8
env:
COPILOT_GITHUB_TOKEN: |
${{ case(
needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0,
needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1,
needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2,
needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3,
needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4,
needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5,
needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6,
needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7,
needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8,
needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9,
'NO COPILOT PAT AVAILABLE')
}}
max-daily-ai-credits: -1
max-ai-credits: -1
strict: true
Expand Down
225 changes: 225 additions & 0 deletions .github/workflows/shared/pat_pool.README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# PAT Pool

Selects a random Copilot PAT from a numbered pool of secrets. This addresses limitations that arise from having a single PAT shared across all agentic workflows, such as rate-limiting.

**This is a stop-gap workaround.** As soon as organization/enterprise billing is available to the dotnet org, this approach will be removed from our workflows.

## Repository Onboarding

To use Agentic Workflows in a dotnet org repository:

1. Follow the instructions for [Configuring Your Repository | Agentic Authoring | GitHub Agentic Workflows][configure-repo]. Use `gh aw` **v0.71.5 or newer**, which supports the agent job dependencies required for this implementation.
2. Copy the `pat_pool.md` and `pat_pool.README.md` files into the repository under `.github/workflows/shared`.
3. Merge those additions into the repository and then follow the instructions for the PAT Creation and Usage below.

**Install or upgrade the `gh aw` CLI and check the version**

```sh
gh extension install github/gh-aw --force
gh aw --version
```

### Environment

Create an environment for the agentic workflows:
- _Configuring these settings requires repo admin permission_
- https://github.com/dotnet/{repo}/settings/environments
- Recommended Name: **copilot-pat-pool**
- Recommended Deployment branches and tags: **Protected branches only**

This environment is used for all agentic workflows, restricting agentic workflows to the repo's protected branches and preventing the workflows from accessing secrets defined for other environments.

## PAT Management

Team members provide PATs into the pool with secret names matching the pattern of `<pool_name>_<0-9>`, such as `COPILOT_PAT_0`.

[Use this link to prefill the PAT creation form with the required settings][create-pat]:

1. **Resource owner** is your **user account**, not an organization.
2. **Copilot Requests (Read)** must be the only permission granted.
3. **8-day expiration** must be used, which enforces a weekly renewal.
4. **Repository access** set to **Public repositories** only.

The **Token Name** _does not_ need to match the secret name and is only visible to the owner of the PAT. It's recommended to use a token name indicating the PAT is used for dotnet org agentic workflows. The **Description** is also only used for your own reference.

Team members providing PATs for workflows should set weekly recurring reminders to regenerate and update their PATs in the PAT pool. With an 8-day expiration, renewal can be done on the same day each week.

## PAT Pool Secrets

For a PAT pool that is specific to an environment, PATs can be added to repositories as **Environment Secrets** for the environment created above. _This requires repo admin permission_.

* **Settings** >
* **Environments** >
* **copilot-pat-pool** (or other environment name) >
* **Add environment secret** (or edit your existing secret)
* Enter your secret name of `COPILOT_PAT_{0-9}` and paste in your PAT

This can also be accomplished using the `gh` CLI, specifying the repo and environment arguments.

```sh
# Register the PAT secret. This will prompt for you to paste the PAT.
gh secret set "<pool_name>_<0-9>" --repo <org>/<repo> --env "copilot-pat-pool"
```

It's also helpful to record who owns each PAT within the pool. To capture which team member is associated with each PAT, a `<pool_name>_<0-9>_<username>` "sidecar secret" can be added alongside the PAT secret to make the username for the PAT pool entry visible. This sidecar secret must have a non-empty value, but it's never consumed, so any value is sufficient.

```sh
# Record a sidecar secret that presents who owns this PAT.
gh secret set "<pool_name>_<0-9>_<username>" --body "<username>" --repo <org>/<repo> --env "copilot-pat-pool"
```

## Workflow Output Attribution

Team members' PATs are _only_ used for the Copilot requests from within the agentic portion of the workflow. All outputs from the workflow use the `github-actions[bot]` account token. Issues, PRs, comments, and all other content generated by the workflow will be attributed to `github-actions[bot]`--not the team member's account or token.

## Usage

The [`pat_pool.md`](./pat_pool.md) workflow import defines a custom job with a `pat_number` output. Consuming workflows need two additions to their frontmatter to import this job and use the PAT number to override the `COPILOT_GITHUB_TOKEN` passed to the workflow's agent job.

```yml
# ###############################################################
# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN.
# Run agentic jobs in an isolated `copilot-pat-pool` environment.
#
# When org-level billing is available, this will be removed.
# See `shared/pat_pool.README.md` for more information.
# ###############################################################
imports:
- uses: shared/pat_pool.md
with:
environment: copilot-pat-pool

environment: copilot-pat-pool

engine:
id: copilot
env:
COPILOT_GITHUB_TOKEN: |
${{ case(
needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0,
needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1,
needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2,
needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3,
needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4,
needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5,
needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6,
needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7,
needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8,
needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9,
'NO COPILOT PAT AVAILABLE')
}}
```

The `COPILOT_GITHUB_TOKEN` expression can be collapsed onto a single line if desired. `gh-aw compile` automatically wires `pat_pool` into the activation and agent jobs' `needs:` graph because of the `needs.pat_pool.` references within the `engine.env` property.

```sh
gh aw compile <workflow-name> --schedule-seed <org>/<repo>
```

### Specifying the environment

The `environment` must be specified both to the `pat_pool.md` import and to the containing workflow to ensure both jobs access the PAT pool from the same environment. The `copilot-pat-pool` environment name is recommended as the isolated environment for agentic workflows that use the PAT pool.

### Customizing the pool

The import declares 10 optional inputs (`COPILOT_PAT_0` through `COPILOT_PAT_9`), each defaulting to `secrets.COPILOT_PAT_#` of the matching number. To point a workflow at a different pool of repository secrets, use the parameterized `uses`/`with` form when importing and pass the substitute secrets as the `COPILOT_PAT_#` inputs:

```yml
imports:
- uses: shared/pat_pool.md
with:
COPILOT_PAT_0: ${{ secrets.MY_TEAM_PAT_0 }}
COPILOT_PAT_1: ${{ secrets.MY_TEAM_PAT_1 }}
# Unspecified inputs default to `secrets.COPILOT_PAT_#` lookups
```

The secrets passed via `with:` must match the secrets referenced in the consuming workflow's `case` expression that overrides `COPILOT_GITHUB_TOKEN`--both sides need to agree on which secret backs each `COPILOT_PAT_#` slot. Update the `case` expression accordingly:

```yml
engine:
id: copilot
env:
COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.MY_TEAM_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.MY_TEAM_PAT_1, ..., 'NO COPILOT PAT AVAILABLE') }}
```

This approach aligns with GitHub's documented guidance for [passing secrets][passing-secrets] between workflows, where the `pat_pool` job returns a PAT number and the `case` statement acts as a secret store to look the PAT secret up based on the selected number.

## Design / Security

There are several details of this implementation that keep our workflows and repositories safe.

1. **Secrets adhere to existing trust boundaries.** The pool of PAT secrets is
provided to a dedicated step within the `pat_pool` job. That job runs
after `pre_activation` and contains only the trusted checkout and action
steps--no untrusted context or input is within scope. The
`select-pat-number` action only references the secret values to determine
which are non-empty, filtering the secret numbers to those with values.
1. **The `pat_pool` job emits only a number, never a secret.** Its sole output,
`pat_number`, is the 0-9 index of the selected PAT (or empty when the pool
is empty). The actual secret materializes only later, in the activation
job's `engine.env` mapping, where the `case()` expression resolves the
number to the matching secret. This follows GitHub's guidance for
[passing secrets][passing-secrets] between jobs or workflows, with the
`case` statement acting as a very simple secret store.
1. **The `select-pat-number` action does not require any permissions.** It
reads only the `COPILOT_PAT_#` environment variables passed to it and writes
only to `GITHUB_OUTPUT`. The job that hosts it sets `permissions:` to the
workflow defaults (no elevated scopes).
1. **The implementation uses supported Agentic Workflow extensibility hooks.**
Defining a custom job inside an [imported workflow file][imports] is
supported by `gh aw compile`. gh-aw automatically
wires `pat_pool` into the activation job's `needs:` graph based on the
`needs.pat_pool.outputs.pat_number` references in `engine.env`. The
[secret override][secret-override] capability supplies the `COPILOT_GITHUB_TOKEN`
value via `engine.env` rather than the default secret of the same name.

Each of the references below contributed to the design and implementation to ensure a secure and reliable design.

## Known Issues

The `pat_pool` import integration requires that the workflow's compilation results in a `pre_activation` job. If nothing in your workflow definition produces a `pre_activation` job, a compilation error will be received.

```text
✗ Failed workflows:
✗ <workflow-name>.md

.github\workflows\<workflow-name>.md:1:1: error: failed to generate YAML: failed to build and validate jobs: job dependency validation failed: job 'pat_pool' depends on non-existent job 'pre_activation'
```

To work around this, add `on.permissions: {}` to your workflow, which forces a no-op `pre_activation` job to be generated.

```yml
on:
permissions: {}
```

See: [Activation 'needs' does not incorporate jobs in engine.env expressions (github/gh-aw#30790)](https://github.com/github/gh-aw/issues/30790)

## References

- [Agentic Workflows CLI Extension][cli-setup]
- [Agentic Authoring][configure-repo]
- [Authentication][authentication]
- [Agentic Workflow Imports][imports]
- [Custom Steps][steps]
- [Custom Jobs][jobs]
- [Job Outputs][job-outputs]
- [Engine Configuration][engine]
- [Engine Environment Variables][engine-vars]
- [Update agentic engine token handling to use user-provided secrets (github/gh-aw#18017)][secret-override]
- [Case Function in Workflow Expressions][case-expression]
- [Passing a secret between jobs or workflows][passing-secrets]

[cli-setup]: https://github.github.com/gh-aw/setup/cli/
[configure-repo]: https://github.github.com/gh-aw/guides/agentic-authoring/#configuring-your-repository
[authentication]: https://github.github.com/gh-aw/reference/auth/
[create-pat]: https://github.com/settings/personal-access-tokens/new?name=dotnet%20org%20agentic%20workflows&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.++Used+for+dotnet+org+workflows.+MUST+be+configured+with+only+Copilot+Requests+permissions+and+user+account+as+resource+owner.+Weekly+expiration+and+required+renewal.&user_copilot_requests=read&expires_in=8
[imports]: https://github.github.com/gh-aw/reference/imports/
[steps]: https://github.github.com/gh-aw/reference/frontmatter/#custom-steps-steps
[jobs]: https://github.github.com/gh-aw/reference/frontmatter/#custom-jobs-jobs
[job-outputs]: https://github.github.com/gh-aw/reference/frontmatter/#job-outputs
[engine]: https://github.github.com/gh-aw/reference/frontmatter/#ai-engine-engine
[engine-vars]: https://github.github.com/gh-aw/reference/engines/#engine-environment-variables
[secret-override]: https://github.com/github/gh-aw/pull/18017
[case-expression]: https://docs.github.com/actions/reference/workflows-and-actions/expressions#case
[passing-secrets]: https://docs.github.com/actions/reference/workflows-and-actions/workflow-commands#example-masking-and-passing-a-secret-between-jobs-or-workflows
Loading
Loading