Skip to content

Commit ca1b29e

Browse files
feat(engine): GitHub App-backed Copilot engine auth (#1326)
* feat(engine): GitHub App-backed Copilot engine auth Add engine.github-app-token front-matter to mint a GitHub App installation token for the Copilot engine (issue #1316), source-clean and without a VSIX or ADO service connection. A new github-app-token ado-script bundle builds an RS256 JWT from the App ID + private key (ADO secret variables), resolves the installation for the owner, exchanges it for an installation access token (optionally repo-scoped), and exposes it as a masked same-job GITHUB_APP_TOKEN. The compiler wires it into GITHUB_TOKEN for the Copilot engine only, in the Agent and Detection jobs; it is never provided to SafeOutputs, user steps, ManualReview, Teardown, or Conclusion. A best-effort revoke step deletes the token after the run. Schema: app-id (literal or variable name), private-key (secret variable), owner, optional repositories, optional api-url (GHES), skip-token-revocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(engine): pin GitHub App token output var; validate revoke step Addresses PR review feedback: - Pin GH_APP_OUTPUT_VAR to the compiler constant (GITHUB_APP_TOKEN) in the mint step. ADO injects pipeline variables as env vars, so without an explicit step-level env entry a pipeline variable named GH_APP_OUTPUT_VAR could silently redirect the minted token, leaving $(GITHUB_APP_TOKEN) empty and breaking auth with no mint-time error. Step env overrides the pipeline-var injection, closing the hole. - Add cfg.validate() to github_app_token_revoke_step_typed for symmetry with the mint step, so neither assumes the other ran first. - Document why the revoke step intentionally omits `set -eo pipefail` (best-effort cleanup; bundle revoke always exits 0; continueOnError). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(engine): pass GitHub App token knobs as argv, not env Strengthens the GH_APP_OUTPUT_VAR fix (PR review): rather than pinning the output-variable name in the step env, move ALL non-secret github-app-token inputs to single-quoted argv flags (--app-id, --owner, --output-var, --repositories, --api-url). ADO injects pipeline variables into a step's env, so any env-sourced knob is shadowable; argv comes only from the compiler- authored script and cannot be shadowed. This eliminates the class of bug at the root for this bundle, not just the one instance. Only the two secrets stay in masked env (GH_APP_PRIVATE_KEY on mint, GH_APP_TOKEN on revoke) — a secret must never appear on a command line. Values are single-quoted (with '\'' escaping) so metacharacters and the runtime- substituted variable-form app-id value cannot expand or word-split. Bundle main()/revoke() now take a parsed CliArgs; env is read only for the secret. Updated Rust + vitest tests and docs accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(engine): harden GitHub App token arg parsing and secrecy advisory Addresses PR review feedback: - parseArgs (github-app-token bundle): never consume a `--flag`-looking token as the value of a preceding flag. Keeps parsing aligned if a future compiler emits a value-less boolean flag (e.g. --debug), instead of silently swallowing the next real flag. Added vitest cases. - Detection job: note that its inline install_and_download_steps_typed is the only ado-script download path there, so a future feature should hoist a single gated download rather than add a second. - Emit a non-blocking compile advisory when engine.github-app-token is configured, reminding the author to store the private-key variable as a secret (the compiler validates the name but cannot verify secrecy). Added unit tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor(engine): compiler-owned defaults for github-app-token variables Aligns github-app-token with ado-aw's GITHUB_TOKEN contract ("the compiler owns the variable name; the user owns the value"): - private-key is now OPTIONAL, defaulting to the compiler-owned secret variable GITHUB_APP_PRIVATE_KEY (set via `ado-aw secrets set`). Set it only to override with a differently-named secret. The common case needs no private-key line. - app-id is a literal-only value (numeric App ID or alphanumeric client ID). The variable-name form is removed, which also fixes a latent bug: the old "all-digits => literal, else variable" heuristic mis-read client IDs like `Iv23li...` as variable names and rendered `$(Iv23li...)`, breaking auth. Output variable stays the fixed internal constant GITHUB_APP_TOKEN. Updated validation, the secrecy advisory (names the effective variable), tests, and docs/engine.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(engine): reject github-app-token on non-copilot engines Addresses PR review: engine.github-app-token was silently a no-op for a non-Copilot engine — the mint/revoke steps emit based on github_app_token().is_some() (engine-agnostic), but GITHUB_TOKEN is only sourced from $(GITHUB_APP_TOKEN) on the Copilot path (copilot_env). Add validate_engine_feature_support: a hard compile error when github-app-token is set with a non-copilot engine.id, run early in compile_pipeline_inner so the author gets a precise message. Mirrors gh-aw's engine-gated config validation (e.g. max-tool-denials, which errors "supported only with engine 'copilot'"). ado-aw only supports copilot today (get_engine rejects others), so this is primarily future-proofing + a precise error, and the load-bearing guard once a second engine is added. Adds unit tests + an integration test for the non-copilot rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(engine): reject negative app-id; tidy detection bundle gating Addresses PR review feedback: - Reject a negative/leading-punctuation app-id. `de_string_or_number` stringifies a negative YAML integer (app-id: -7654321 -> "-7654321"), which passed the [A-Za-z0-9._-] charset check because '-' is allowed, yielding a bad JWT `iss` at runtime. validate() now requires the first character to be alphanumeric. Added unit tests. - Extract detection_job_needs_ado_script_bundle() predicate so the Detection job stages the ado-script bundle once, and future detection-only consumers OR into it (mirrors the Agent-job predicate in AdoScriptExtension) rather than adding a second download. - Add BashStep::with_continue_on_error builder so the revoke step is a clean builder chain instead of interrupting it with a direct field mutation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: document github-app-token advisory, engine-gating, and authoring prompts - engine.md: note the compile-time secrecy advisory and that github-app-token is a hard error on non-copilot engines (previously undocumented behaviors). - create/update/debug authoring prompts: add github-app-token guidance (org-backed Copilot auth) — a create-time engine note, an update scenario, and a debug troubleshooting entry — each linking to docs/engine.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: fix stale github-app-token struct-level doc Addresses PR review: the GithubAppTokenConfig struct-level doc comment still described app-id (and private-key) as "ADO pipeline variable names" with an `app-id: GH_APP_ID` example, contradicting the field-level doc and the actual schema. app-id is a literal value (numeric App ID or client ID); only private-key names a secret variable. Fixed the opening sentence and the example (app-id: 1234567). IDE hover / rustdoc now match runtime behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3607e95 commit ca1b29e

21 files changed

Lines changed: 2289 additions & 33 deletions

AGENTS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ fail-closed and only pauses when the agent actually proposed a reviewed output.
264264
│ ├── exec-context-repo/ # Repository identity context source (bundled to exec-context-repo.js)
265265
│ ├── conclusion/ # Conclusion-job reporter source (bundled to conclusion.js)
266266
│ ├── approval-summary/ # Safe-outputs summary renderer (bundled to approval-summary.js; end-of-Agent-job summary tab)
267+
│ ├── github-app-token/ # GitHub App token minter (bundled to github-app-token.js; mints installation token in Agent + Detection when engine.github-app-token is set)
267268
│ └── shared/ # Shared modules across bundles (auth, ado-client, env-facts, types.gen.ts)
268269
├── tests/ # Integration tests and fixtures
269270
├── docs/ # Per-concept reference documentation (see index below)
@@ -379,7 +380,11 @@ index to jump to the right page.
379380
- [`docs/ado-script.md`](docs/ado-script.md)`ado-script` workspace
380381
(`scripts/ado-script/`): the bundled TypeScript runtime helpers
381382
(`gate.js`, `import.js`, the execution-context `exec-context-*.js`
382-
bundles, `conclusion.js`, and `approval-summary.js`), schemars-driven
383+
- [`docs/ado-script.md`](docs/ado-script.md)`ado-script` workspace
384+
(`scripts/ado-script/`): the bundled TypeScript runtime helpers
385+
(`gate.js`, `import.js`, the execution-context `exec-context-*.js`
386+
bundles, `conclusion.js`, `approval-summary.js`, and
387+
`github-app-token.js`), schemars-driven
383388
type codegen, the A2 design decision, and the bundle env contract
384389
modelled in `src/compile/ado_bundle.rs`.
385390
- [`docs/local-development.md`](docs/local-development.md) — local development

docs/ado-script.md

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ pipeline** as runtime helpers. Today it produces thirteen bundles:
5656
review is configured), and attaches it to the build's
5757
`ado-aw-safe-outputs` summary tab via `##vso[task.uploadsummary]`. See
5858
[`safe-outputs.md`](safe-outputs.md).
59+
- `github-app-token.js` — GitHub App token minter that runs immediately
60+
before the Copilot invocation in the **Agent and Detection jobs** when
61+
`engine.github-app-token` is configured. Builds an RS256 JWT from the App ID
62+
(argv) + private key (masked env secret), resolves the installation for the
63+
owner, exchanges it for an installation access token, and exposes it as a
64+
masked same-job `GITHUB_APP_TOKEN`. Invoked again with a `revoke` argument
65+
after the Copilot run (best-effort) to delete the token via
66+
`DELETE /installation/token`. Compiler-owned, non-secret inputs (`--app-id`,
67+
`--owner`, `--output-var`, `--repositories`, `--api-url`) are argv flags, not
68+
env vars, so a pipeline variable can't shadow them (only the private key /
69+
minted token ride in masked env). Runs outside AWF. See
70+
[`engine.md`](engine.md#github-app-backed-copilot-engine-auth).
5971

6072
> **Internal-only.** `ado-script` is not a user-facing front-matter
6173
> feature. Authors never write an `ado-script:` block in their agent
@@ -328,6 +340,16 @@ collection URI is likewise read from the auto-injected `SYSTEM_COLLECTIONURI`
328340
(`scripts/ado-script/src/shared/auth.ts::getWebApi`, see #1307), so it
329341
is never part of a step's env contract.
330342

343+
Not every bundle authenticates to Azure DevOps. The `github-app-token` bundle
344+
(issue #1316) is `BundleAuth::None`: it authenticates to the **GitHub** API with
345+
its own App JWT / minted installation token, so it carries no
346+
`SYSTEM_ACCESSTOKEN` and no ADO predefined mirrors. Its only env var is the
347+
masked private-key secret (`GH_APP_PRIVATE_KEY`, and `GH_APP_TOKEN` for revoke);
348+
every other, non-secret input (App ID, owner, repositories, output-variable
349+
name, api-url) is a single-quoted **argv flag** rather than an env var, so a
350+
pipeline variable can never shadow it (ADO injects pipeline variables into a
351+
step's env, but argv comes only from the compiler-authored script).
352+
331353

332354
## End-to-end data flow
333355

@@ -492,6 +514,9 @@ scripts/ado-script/
492514
│ └── conclusion/ # conclusion.js entry point + Conclusion-job reporter
493515
│ ├── index.ts # main(): inspect upstream results + safe-outputs manifest → file/append work items
494516
│ └── __tests__/ # unit tests for signal detection and work-item filing behaviour
517+
│ └── github-app-token/ # github-app-token.js entry point + GitHub App token minter
518+
│ ├── index.ts # main(): RS256 JWT → resolve installation → mint installation token → masked GITHUB_APP_TOKEN
519+
│ └── __tests__/ # unit tests for JWT signing / installation resolution / token minting
495520
├── test/ # End-to-end smoke tests (gate, import, exec-context-pr)
496521
├── gate.js # ncc bundle output (gitignored)
497522
├── import.js # ncc bundle output (gitignored)
@@ -505,23 +530,19 @@ scripts/ado-script/
505530
├── exec-context-pr-checks.js # ncc bundle output (gitignored)
506531
├── exec-context-repo.js # ncc bundle output (gitignored)
507532
├── conclusion.js # ncc bundle output (gitignored)
508-
└── approval-summary.js # ncc bundle output (gitignored)
533+
├── approval-summary.js # ncc bundle output (gitignored)
534+
└── github-app-token.js # ncc bundle output (gitignored)
509535
```
510536

511537
The release workflow (`.github/workflows/release.yml`) runs
512-
`npm ci && npm run build`, then zips `scripts/ado-script/gate.js`,
513-
`scripts/ado-script/import.js`,
514-
`scripts/ado-script/exec-context-pr.js`,
515-
`scripts/ado-script/exec-context-pr-synth.js`,
516-
`scripts/ado-script/exec-context-manual.js`,
517-
`scripts/ado-script/exec-context-pipeline.js`,
518-
`scripts/ado-script/exec-context-ci-push.js`,
519-
`scripts/ado-script/exec-context-workitem.js`,
520-
`scripts/ado-script/exec-context-schedule.js`,
521-
`scripts/ado-script/exec-context-pr-checks.js`,
522-
`scripts/ado-script/exec-context-repo.js`,
523-
`scripts/ado-script/conclusion.js`, and
524-
`scripts/ado-script/approval-summary.js` into the
538+
`npm ci && npm run build`, then globs `scripts/ado-script/*.js` — which
539+
captures every bundle, including `gate.js`, `import.js`,
540+
`exec-context-pr.js`, `exec-context-pr-synth.js`,
541+
`exec-context-manual.js`, `exec-context-pipeline.js`,
542+
`exec-context-ci-push.js`, `exec-context-workitem.js`,
543+
`exec-context-schedule.js`, `exec-context-pr-checks.js`,
544+
`exec-context-repo.js`, `conclusion.js`, `approval-summary.js`, and
545+
`github-app-token.js` — into the
525546
`ado-script.zip` release asset. Pipelines download that asset at
526547
runtime by URL pinned to the compiler's `CARGO_PKG_VERSION`, verify
527548
its SHA-256 against the `checksums.txt` asset, then extract.

docs/engine.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ engine:
3030
| `args` | list | `[]` | Custom CLI arguments appended after compiler-generated args. Subject to shell-safety validation and blocked from overriding compiler-controlled flags (`--prompt`, `--additional-mcp-config`, `--allow-tool`, `--allow-all-tools`, `--allow-all-paths`, `--disable-builtin-mcps`, `--no-ask-user`, `--ask-user`). |
3131
| `env` | map | *(none)* | Engine-specific environment variables merged into the sandbox step's `env:` block. Keys must be valid env var names. Values are literal-only and must not contain ADO expressions (`$(`, `${{`, `$[`) or pipeline command injection (`##vso[`), **except** the Copilot BYOM provider keys (`COPILOT_PROVIDER_BASE_URL`, `COPILOT_PROVIDER_API_KEY`, `COPILOT_PROVIDER_BEARER_TOKEN`, `COPILOT_PROVIDER_WIRE_API`), which may carry an ADO macro (`$(...)`) expression — see [Copilot BYOM / BYOK provider configuration](#copilot-byom--byok-provider-configuration). Compiler-controlled keys (`GITHUB_TOKEN`, `PATH`, `BASH_ENV`, etc.) are blocked. |
3232
| `command` | string | *(none)* | Custom engine executable path (skips the default engine binary installation — NuGet for `target: 1es`, GitHub Releases for all other targets). The path must be accessible inside the AWF container (e.g., `/tmp/...` or workspace-mounted paths). |
33+
| `github-app-token` | map | *(none)* | GitHub App-backed Copilot engine authentication. When set, the compiler mints (and, by default, revokes) a GitHub App installation token in the Agent and Detection jobs and sources `GITHUB_TOKEN` from it (for Copilot only). See [GitHub App-backed Copilot engine auth](#github-app-backed-copilot-engine-auth). |
3334

3435

3536
### `timeout-minutes`
@@ -42,6 +43,118 @@ The `timeout-minutes` field sets a wall-clock limit (in minutes) for the entire
4243

4344
When omitted, Azure DevOps uses its default job timeout (60 minutes). When set, the compiler emits `timeoutInMinutes: <value>` on the agentic job.
4445

46+
### GitHub App-backed Copilot engine auth
47+
48+
By default the Copilot engine authenticates with the `GITHUB_TOKEN` pipeline
49+
variable you provide (`ado-aw secrets set GITHUB_TOKEN <pat>`). For
50+
organization-backed Copilot authentication you can instead have the compiler
51+
mint a **GitHub App installation access token** at runtime — mirroring
52+
[gh-aw's](https://github.com/githubnext/gh-aw) `create-github-app-token` model,
53+
adapted to Azure DevOps.
54+
55+
```yaml
56+
engine:
57+
id: copilot
58+
github-app-token:
59+
app-id: 1234567 # literal App ID or client ID (required)
60+
owner: octo-org # installation owner (org or user login)
61+
repositories: [octo-repo] # optional; scopes the token to these repos
62+
# api-url: https://ghe.example.com/api/v3 # optional; GHES base URL
63+
# skip-token-revocation: false # optional; revoke by default
64+
# private-key: MY_SECRET_VAR # optional override; see below
65+
```
66+
67+
Store the private key once and you're done:
68+
69+
```bash
70+
ado-aw secrets set GITHUB_APP_PRIVATE_KEY "$(cat app-private-key.pem)"
71+
```
72+
73+
| Field | Required | Description |
74+
|-------|----------|-------------|
75+
| `app-id` | yes | The GitHub App ID — a **literal** value: either a numeric App ID (e.g. `1234567`, quoted or unquoted) or an alphanumeric client ID (e.g. `Iv23liABC…`). The App ID is not secret (it is plain per-app config, like `owner`), so it is written verbatim, not indirected through a variable. |
76+
| `owner` | yes | GitHub installation owner (organization or user login). |
77+
| `repositories` | no | Repository names (owner-relative) to scope the installation token to. Omit to span every repository the installation grants. |
78+
| `api-url` | no | GitHub API base URL. Defaults to `https://api.github.com` (GHEC). For GitHub Enterprise Server, set the `/api/v3` base URL (e.g. `https://ghe.example.com/api/v3`). Must be an `https://` URL. |
79+
| `skip-token-revocation` | no | When `true`, do not revoke the minted token after the Copilot run. Defaults to `false` (the token is revoked — see below). |
80+
| `private-key` | no | Name of the ADO **secret** pipeline variable holding the private key (PEM). **Defaults to `GITHUB_APP_PRIVATE_KEY`** — the compiler owns the name, exactly like `GITHUB_TOKEN`, so the common case sets no field and just runs `ado-aw secrets set GITHUB_APP_PRIVATE_KEY …`. Set this only to point at a differently-named secret (e.g. reusing an existing variable). |
81+
82+
#### Setup
83+
84+
1. Create the GitHub App and install it on the owning org/user, ensuring the
85+
installation is suitable for Copilot organization-backed
86+
authentication/billing in your tenant. Copilot capability is granted on the
87+
**App/org side** — the installation token inherits it automatically; you do
88+
not (and cannot) configure it here.
89+
2. Store the private key as an ADO **secret** pipeline variable — the default
90+
name `GITHUB_APP_PRIVATE_KEY` unless you set a `private-key` override:
91+
92+
```bash
93+
ado-aw secrets set GITHUB_APP_PRIVATE_KEY "$(cat app-private-key.pem)"
94+
```
95+
3. Set `engine.github-app-token` (`app-id` + `owner` at minimum) and compile.
96+
Each compile prints a non-blocking advisory reminding you to store the
97+
private-key variable as a secret — this is expected (the compiler cannot
98+
verify secrecy itself).
99+
100+
#### What the compiler generates
101+
102+
- A **token-mint step** (the `github-app-token` ado-script bundle) runs
103+
immediately before the Copilot invocation in **both** the Agent and Detection
104+
jobs. It builds a short-lived RS256 JWT from the App ID + private key,
105+
resolves the installation for `owner`, exchanges it for an installation
106+
access token (optionally scoped to `repositories`), and exposes it as a
107+
**masked, same-job** `GITHUB_APP_TOKEN` variable.
108+
- The Copilot engine's `GITHUB_TOKEN` is then sourced from `$(GITHUB_APP_TOKEN)`
109+
instead of the operator-provided `$(GITHUB_TOKEN)` variable.
110+
- A **token-revocation step** runs after the Copilot invocation in both jobs
111+
(unless `skip-token-revocation: true`). It deletes the minted token
112+
(`DELETE /installation/token`) so it does not remain valid for its full ~1h
113+
lifetime — matching `actions/create-github-app-token`'s default. Revocation is
114+
best-effort (`always()` + `continueOnError`) and never fails the build.
115+
- The token is **never** provided to SafeOutputs, user-authored `steps:`,
116+
ManualReview, Teardown, or Conclusion.
117+
118+
The mint/revoke steps run **outside** the AWF network sandbox (like the
119+
ADO-token and execution-context steps), reaching the GitHub API over the build
120+
agent pool's normal network — no AWF `network.allowed` entry is required.
121+
122+
#### Scope and boundaries — what this token is *not*
123+
124+
- **ADO API permissions** (`permissions.read` / `permissions.write`) are
125+
entirely separate: they describe Azure DevOps OAuth/service-connection tokens
126+
used by the pipeline and Stage 3 executor. The GitHub App token has no effect
127+
on them.
128+
- The GitHub App token is for **Copilot engine authentication only**. It does
129+
**not** authenticate the `gh` CLI, grant GitHub MCP permissions, or give the
130+
agent sandbox GitHub write access.
131+
- There is **no `permissions:` sub-field** to scope the token. Copilot access
132+
rides on the App's own granted capability (configured App/org-side); narrowing
133+
the installation token's permissions at mint time cannot grant Copilot access
134+
and risks stripping the capability it needs.
135+
136+
#### Notes and limitations
137+
138+
- **Copilot engine only.** `github-app-token` is rejected at compile time for
139+
any other `engine.id` (the minted token is wired into `GITHUB_TOKEN` only on
140+
the Copilot path), so a misconfiguration fails fast rather than silently
141+
no-opping.
142+
- **Secrecy is your responsibility.** The compiler validates the private-key
143+
*variable name* but cannot verify the ADO variable is actually marked secret,
144+
so every compile of a `github-app-token` workflow emits an advisory:
145+
`Warning: engine.github-app-token uses pipeline variable '<name>' … Ensure
146+
'<name>' is stored as a SECRET …`. It is non-blocking — heed it by setting the
147+
value with `ado-aw secrets set` (which stores it as a secret).
148+
- **GHEC by default; GHES via `api-url`.** The mint/revoke steps target
149+
`https://api.github.com` unless you set `api-url` to your GitHub Enterprise
150+
Server `/api/v3` base URL. This is independent of `engine.api-target`, which
151+
configures the Copilot API host, not the GitHub App API host.
152+
- **`openssl` is not required** — the token is minted with Node's built-in
153+
crypto. The build agent needs Node (installed automatically) and network
154+
access to the GitHub API host.
155+
- You may still need to pin `engine.version` until the relevant Copilot CLI auth
156+
behavior is broadly available.
157+
45158
### Copilot BYOM / BYOK provider configuration
46159

47160
The Copilot engine can route requests to an external LLM provider — for example a

prompts/create-ado-agentic-workflow.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ engine:
8181
timeout-minutes: 30
8282
```
8383

84+
**Org-backed Copilot auth (GitHub App).** By default Copilot authenticates with
85+
the `GITHUB_TOKEN` pipeline variable. For organization-backed authentication,
86+
add a `github-app-token` block so the compiler mints a GitHub App installation
87+
token at runtime (Copilot engine only):
88+
```yaml
89+
engine:
90+
id: copilot
91+
github-app-token:
92+
app-id: 1234567 # literal App ID or client ID
93+
owner: octo-org # installation owner (org or user)
94+
# repositories: [octo-repo] # optional; scopes the token
95+
```
96+
Store the private key once with `ado-aw secrets set GITHUB_APP_PRIVATE_KEY
97+
"$(cat key.pem)"`. Only add this when the workflow explicitly needs org-backed
98+
Copilot auth — omit it otherwise. See
99+
[`docs/engine.md`](../docs/engine.md#github-app-backed-copilot-engine-auth) for
100+
the full field reference (`private-key` override, `api-url` for GHES,
101+
`skip-token-revocation`).
102+
84103
### Step 3 — Schedule
85104

86105
Use the **fuzzy schedule syntax** (deterministic time scattering based on agent name hash prevents load spikes). Omit `on.schedule` (or omit `on:` entirely) for manual/trigger-only pipelines.

prompts/debug-ado-agentic-workflow.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ network:
314314
timeout-minutes: 120
315315
```
316316
- **API rate limiting**: The model provider is rate-limiting requests. Check Copilot CLI logs for 429 responses.
317+
- **GitHub App auth failures** (when `engine.github-app-token` is set): if Copilot fails to authenticate, check the `Mint GitHub App token` step logs in the Agent (and Detection) job. Common causes: the `GITHUB_APP_PRIVATE_KEY` secret (or your `private-key` override) is unset or not marked secret; a wrong `app-id`/`owner` (the App must be installed on that owner); or GHES without `api-url` set to the `/api/v3` base URL. `github-app-token` is Copilot-engine only — the compiler rejects it on other engines. See [`docs/engine.md`](../docs/engine.md#github-app-backed-copilot-engine-auth).
317318

318319
### Agent Tool Errors
319320

prompts/update-ado-agentic-workflow.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,34 @@ permissions:
229229

230230
`permissions.write` is **optional** — the Stage 3 executor defaults to `$(System.AccessToken)`. Set it only when you need cross-org writes or named-identity attribution instead of `Project Collection Build Service`.
231231

232+
### Enabling GitHub App-backed Copilot Auth
233+
234+
Add a `github-app-token` block under `engine:` so Copilot authenticates with an
235+
org-backed GitHub App installation token (Copilot engine only):
236+
237+
```yaml
238+
engine:
239+
id: copilot
240+
github-app-token:
241+
app-id: 1234567 # literal App ID or client ID
242+
owner: octo-org # installation owner (org or user)
243+
# repositories: [octo-repo] # optional; scopes the token
244+
# api-url: https://ghe.example.com/api/v3 # optional; GHES
245+
# private-key: MY_SECRET_VAR # optional; defaults to GITHUB_APP_PRIVATE_KEY
246+
# skip-token-revocation: false # optional; token is revoked by default
247+
```
248+
249+
Then store the private key as a secret:
250+
```bash
251+
ado-aw secrets set GITHUB_APP_PRIVATE_KEY "$(cat app-private-key.pem)"
252+
```
253+
254+
Notes: this is distinct from `permissions:` (ADO API tokens) — it only sources
255+
the Copilot engine's `GITHUB_TOKEN`. Compile prints a non-blocking advisory
256+
reminding you to mark the private-key variable secret. See
257+
[`docs/engine.md`](../docs/engine.md#github-app-backed-copilot-engine-auth).
258+
Requires recompilation.
259+
232260
### Adding Pre/Post Steps
233261

234262
**Inline steps** run inside the `Agent` job:

scripts/ado-script/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ exec-context-pr-checks.js
1313
exec-context-repo.js
1414
approval-summary.js
1515
conclusion.js
16+
github-app-token.js
1617
schema
1718
*.tsbuildinfo

0 commit comments

Comments
 (0)