From 159536d5c1458e09206ffbe67c0c06aad8f21ec1 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 10:01:40 -0500 Subject: [PATCH 01/10] [skills][workflows] Add update-androidsdk-packages skill and skill-runner workflow Add a new project-local Copilot skill, update-androidsdk-packages, that automates refreshing the stable Android SDK package pins (versions, revisions, URLs, SHA-256 hashes) that src/androidsdk/androidsdk.csproj consumes from Configuration.props and src/androidsdk/androidsdk.targets, modeled on the update pattern in PR #12371. The skill enforces two hard rules: 1. Never touch the Android NDK (_XAAndroidNdk*/XAAndroidNdkHash*) -- it has its own release cadence and is out of scope here. 2. Never add a new Android platform API level to _PlatformPackage -- only refresh the revision/archive/hash of API levels already in the catalog. If Google has published a newer stable platform level than the highest one already present (e.g. platform 37.1 while the catalog tops out at 37.0), do not add it, but always surface that fact in the final summary every run, regardless of whether the request otherwise mentioned platform levels. Bundled resources: - scripts/fetch_repo_package.cs, scripts/sha256_of_url.cs -- C# file-based `dotnet run` apps (matching the ci-status skill's ci_failures.cs convention) that query Google's repository2-3.xml SDK manifest and compute authoritative SHA-256 hashes by downloading archives into a scratch temp file, since Google's manifests only publish SHA-1. Preview/alpha/beta/RC/canary releases are filtered with a word-boundary regex plus a check, since channelRef alone is not a reliable stable/preview signal. - references/package-catalog.md -- mapping from each package family to its manifest path and Configuration.props/androidsdk.targets properties. - evals/evals.json -- realistic prompts covering normal updates and both hard-rule exclusions. Both scripts were reviewed via a rubber-duck pass that found and fixed: false-positive preview matches inside substrings like "sources", a missing preview signal, channelRef being read from the wrong XML location, relative archive URLs not resolved against the manifest base URI, a Uri constructor call outside its try block, and a cleanup File.Delete that could mask the real success/failure outcome. Validated by building build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj (required for androidsdk.csproj to evaluate) followed by src/androidsdk/androidsdk.targets evaluation, live-testing both C# scripts against Google's real manifest, and confirming a clean diff. Also add a new gh-aw agentic workflow, skill-runner, that runs repository Copilot skills unattended on a weekly schedule or on demand: - workflow_dispatch exposes a `skill` dropdown (currently just update-androidsdk-packages) so a caller can pick which skill runs. When no skill is explicitly selected (scheduled runs, or dispatch left blank), a bootstrap step picks uniformly at random among the eligible skills -- the same pattern nightly-fix-finder uses for its scan scripts -- and records the choice for the prompt to read. - The prompt is skill-agnostic: it loads whichever SKILL.md was selected and follows it verbatim, consulting a "Known Skills" table only for workflow-level context (always-report rules, prerequisite bootstraps) that supplements rather than overrides the skill file. - Every run reports its outcome -- including pure no-ops, a newly published platform level found upstream, and any errors -- on a self-deduplicating tracking issue (close-older-issues, 30-day expiry). A PR is opened only when validated changes are produced, restricted to Configuration.props and src/androidsdk/androidsdk.targets. - An "Adding a New Skill" section documents the steps to wire in additional skills: dropdown + ELIGIBLE_SKILLS array entry, Known Skills table row, optional prerequisite-bootstrap elif branch, allowed-files update, and recompiling with `gh aw compile`. Compiled cleanly with `gh aw compile --approve` (the PAT-pool secrets this workflow references are the same ones already approved for nightly-fix-finder). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../update-androidsdk-packages/SKILL.md | 170 ++ .../evals/evals.json | 29 + .../references/package-catalog.md | 40 + .../scripts/fetch_repo_package.cs | 170 ++ .../scripts/sha256_of_url.cs | 80 + .github/workflows/skill-runner.lock.yml | 1807 +++++++++++++++++ .github/workflows/skill-runner.md | 313 +++ 7 files changed, 2609 insertions(+) create mode 100644 .github/skills/update-androidsdk-packages/SKILL.md create mode 100644 .github/skills/update-androidsdk-packages/evals/evals.json create mode 100644 .github/skills/update-androidsdk-packages/references/package-catalog.md create mode 100644 .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs create mode 100644 .github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs create mode 100644 .github/workflows/skill-runner.lock.yml create mode 100644 .github/workflows/skill-runner.md diff --git a/.github/skills/update-androidsdk-packages/SKILL.md b/.github/skills/update-androidsdk-packages/SKILL.md new file mode 100644 index 00000000000..e28c6005c6e --- /dev/null +++ b/.github/skills/update-androidsdk-packages/SKILL.md @@ -0,0 +1,170 @@ +--- +name: update-androidsdk-packages +description: >- + Update stable Android SDK package pins (versions, revisions, URLs, SHA-256 hashes) in + src/androidsdk/androidsdk.targets and Configuration.props for androidsdk.csproj. Use for + requests to update/refresh Android SDK packages, tools, or archive hashes. Never for the + Android NDK (out of scope) or unrelated NuGet/MSBuild SDK versions. +--- + +# Update Android SDK packages + +`src/androidsdk/androidsdk.csproj` installs the Android SDK by downloading a fixed catalog of +Google-published zips and verifying each one's SHA-256 against a hardcoded hash. The catalog +lives in two files: + +- **`Configuration.props`** — version numbers, `PkgRevision`s, and every `XA*Hash*`/`CommandLineTools*` property. +- **`src/androidsdk/androidsdk.targets`** — the `_AndroidSdkPackage` item list (one item per zip a host downloads) and the `_PlatformPackage` catalog (one item per Android platform API already shipped). + +Google republishes tool revisions on their own cadence; this skill brings those two files back in +sync with Google's *current stable* releases with a minimal, reviewable diff — matching the shape of PR #12371, which did exactly this (build-tools/platform-tools/cmdline-tools/cmake/emulator/sources/platform revisions all bumped, hashes recomputed, and per-arch macOS cmdline-tools support added when Apple Silicon archives showed up). + +## Two hard rules — read these before touching anything + +**1. Never touch the Android NDK.** `_XAAndroidNdkRelease`, `_XAAndroidNdkPkgRevision`, and every +`XAAndroidNdkHash*` property in `Configuration.props`, plus the `android-ndk-r$(_XAAndroidNdkRelease)-*` +package entry in `androidsdk.targets`, are intentionally out of scope for this skill. The NDK has +its own release cadence and compatibility constraints that this workflow doesn't manage — leave +every NDK-related line exactly as you found it, even if Google has published a newer NDK. + +**2. Never add a new Android platform API level to `_PlatformPackage` — but ALWAYS report when one exists.** +This item group in `androidsdk.targets` is the list of Android platform SDKs this repo ships +against; adding an entry is a deliberate, separate decision (usually tied to a new Android OS +release and API surface changes elsewhere in the repo), not something to do as a side effect of a +routine package refresh. If Google has published a newer *revision* of a platform API level that's +already in the catalog (e.g. `platform-37.0_r01` → `platform-37.0_r02`, or a new extension level +like `_ext19`), update that existing entry's `Include`, `Hash`, and any `IsLatestStable`/extension +revision in place. But if Google has published an entirely new API level not yet in the catalog +(e.g. the catalog tops out at `37.0` but the manifest also lists a stable `37.1`), do not add it — +that decision is out of scope here. **Every time this happens, you must still surface it**: always +check the manifest for any stable platform level newer than the highest one already in +`_PlatformPackage` (not just when the user happens to ask), and call it out explicitly in your +final summary (step 6 below) — even if the user didn't ask about platform levels at all and even if +nothing else in the catalog needed updating this run. Silence here is a bug: the whole point of this +rule is that a human decides whether/when to onboard a new API level, and they can't decide on +something they were never told about. + +## Workflow + +### 1. Read the current catalog + +Read `Configuration.props` (the `XA*`/`CommandLineTools*`/`Emulator*` properties, roughly lines +100-190) and `src/androidsdk/androidsdk.targets` (`_PlatformPackage` item group near the top, and +the `_AndroidSdkPackage` item group with per-host `Include`s) so you know exactly which package +families and API levels already exist. You are only ever refreshing what's already there. + +### 2. Look up what Google currently publishes + +Google's canonical SDK manifest is `https://dl.google.com/android/repository/repository2-3.xml` +(historically referenced via `dl-ssl.google.com` — same content, prefer `dl.google.com`). System +images live in a separate manifest per API level tree, e.g. +`https://dl.google.com/android/repository/sys-img/android/sys-img2-3.xml`. Use the bundled helper +to query it instead of hand-parsing XML in your head: + +```bash +dotnet run .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs -- --path build-tools +dotnet run .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs -- --path "platforms;android-37" --archives +dotnet run .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs -- --path emulator --archives +``` + +(These are C# file-based apps, matching the `ci_failures.cs` convention used by the `ci-status` skill — first run restores/builds, so allow a few extra seconds.) + +The script sorts matches by revision (newest first) and flags anything whose path/display-name +looks like a preview build. **Treat that flag as a hint, not ground truth** — Google's +`channelRef` metadata is not a reliable stable/preview signal by itself (some genuinely-stable +packages carry a non-zero channel id, and freshly-promoted stable packages can briefly still show +old channel numbers). Cross-check the display name and version string yourself: a real stable +release reads like `36.0.1` or `28c`, not `37.0.0-rc1`, `2025.09.15-alpha01`, or anything with +`beta`/`canary`/`preview` in it. When genuinely unsure whether a release is stable, prefer the +previous confirmed-stable revision over guessing. + +Reference `references/package-catalog.md` for the mapping between each `androidsdk.targets` entry, +its manifest `path`, and its `Configuration.props` properties — it documents the current package +families (build-tools, platform-tools, cmdline-tools, cmake, emulator, the API 29 system image, +m2repository, docs, sources, platforms) so you don't have to re-derive the mapping from scratch +each time. + +### 3. Get authoritative SHA-256 hashes + +Google's manifests only publish SHA-1 checksums, but `Configuration.props` pins SHA-256 (MSBuild's +`GetFileHash` task requires SHA-256+, see the comment atop `androidsdk.targets`). **Never invent or +guess a SHA-256** — either: + +- Find it already published somewhere authoritative Google links to alongside the release (rare + for these particular archives), or +- Compute it yourself by downloading the exact archive URL and hashing it: + +```bash +dotnet run .github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip +``` + +This script downloads into a scratch temp file (not `$(AndroidToolchainCacheDirectory)`, +which defaults to `$HOME/android-archives` and is the repo's real download cache) and deletes the +file once hashed, so it never pollutes the cache or shows up as an untracked file in `git status`. +Do this for every host archive you're updating — Windows, Linux, macOS, and macOS arm64 when Google +publishes a separate Apple Silicon archive (as it started doing for command-line tools; check +whether the manifest/download page now lists a `mac_arm64` alongside `mac_x86_64` before assuming +one shared macOS zip still covers both). + +### 4. Update per-host and per-arch hashes and versions + +- Bump the shared version/revision property once (e.g. `XABuildToolsVersion`, `XAPlatformToolsVersion`, + `CommandLineToolsFolder`/`CommandLineToolsVersion`, `AndroidCmakeVersion`, `EmulatorVersion` + + `EmulatorPkgRevision`). +- Update every `*HashMacOS`/`*HashLinux`/`*HashWindows` (and `*HashMacOSArm64`/`*HashMacOSx64` where + they exist) for that package family with the freshly computed SHA-256. +- If Google has started publishing an architecture-specific macOS archive for a package that + previously had one shared macOS zip (as happened for command-line tools in PR #12371), add the + arm64 variant the same way that PR did: a second `_AndroidSdkPackage` item gated on + `'$(_IsArm64Apple)' == 'true'` alongside the existing x86_64 item gated on + `'$(_IsArm64Apple)' != 'true'`, plus a new `*HashMacOSArm64` property. Follow the existing + `emulator`/system-image entries in `androidsdk.targets` as the pattern for per-arch conditions — + `_IsArm64Apple` is already computed in `Configuration.props`. +- For platform packages already in `_PlatformPackage`, update the `Include` (new package/revision + string, e.g. `platform-37.0_r01` → `platform-37.0_r02`) and `Hash` in place. Update the + extension-level suffix too when Google has published one for an API level that already uses it + (e.g. `platform-34-ext7_r02` → `platform-34-ext12_r01`) — do not introduce an extension suffix for + an API level that never had one, or vice versa, without a clear reason from the manifest. +- The `source-NN_r0M.zip` sources package and `XAAndroidSourcesHash` should track whichever API level + is `IsLatestStable="true"` in `_PlatformPackage` (see the existing `` path — it embeds + the API level, e.g. `\sources\android-37.0`). Update both the zip name/Destination and the hash + together if the latest stable API level's source archive changed. + +### 5. Validate before finishing + +Run these in order — do not skip the BootstrapTasks build; `androidsdk.csproj` uses +`UnzipDirectoryChildren`, a task defined in that assembly, and fails at evaluation time without it: + +```bash +dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal +dotnet build src/androidsdk/androidsdk.csproj --no-restore -v:minimal +``` + +The second build downloads and extracts the whole SDK for the current host — expect it to take a +few minutes and use real disk space/network. If that's not acceptable in your environment, at +least confirm MSBuild evaluation succeeds without downloading anything: + +```bash +dotnet build src/androidsdk/androidsdk.csproj --no-restore -v:minimal -t:_AddPlatformPackagesToInstall +``` + +Also check: +- **XML validity** — both edited files still parse (`dotnet build` will fail loudly on malformed XML, but a quick sanity check like `powershell -Command "[xml](Get-Content src/androidsdk/androidsdk.targets)"` catches issues faster). +- **Diff cleanliness** — `git status` and `git diff` should show changes *only* in `Configuration.props` and `src/androidsdk/androidsdk.targets` (plus `package.xml.in` only if you deliberately changed the generated-package-xml template, which is rare). No stray temp files from hashing (the `sha256_of_url.cs` script cleans up after itself; double check if you downloaded anything manually instead). +- **The two hard rules above** — diff the NDK properties and the `_PlatformPackage` item count/API-level set against `git diff` to confirm neither was touched/expanded. +- **Formatting** — match the existing tab indentation and column alignment in both files (several `_PlatformPackage`/`_AndroidSdkPackage` lines are hand-aligned with extra spaces before ``/`` — preserve that style rather than reformatting the whole block). + +### 6. Summarize what changed + +When done, report which package families were bumped (old → new version/revision), which hosts/ +archs had hashes recomputed, whether a new macOS-arm64-specific archive was added, and confirm the +NDK was left untouched. **Always** include a line about the platform catalog check from rule 2 +above — either "no newer stable platform level exists upstream" or, if one does, name it explicitly +(e.g. "note: platform 37.1 is published upstream but was intentionally not added — the catalog +still tops out at 37.0; add it in a separate change if desired"). Report this every time, regardless +of whether the user's request mentioned platform levels at all. + +## Reference + +- `references/package-catalog.md` — the current package families, their manifest paths, and their + `Configuration.props`/`androidsdk.targets` locations. diff --git a/.github/skills/update-androidsdk-packages/evals/evals.json b/.github/skills/update-androidsdk-packages/evals/evals.json new file mode 100644 index 00000000000..2c129bea97d --- /dev/null +++ b/.github/skills/update-androidsdk-packages/evals/evals.json @@ -0,0 +1,29 @@ +{ + "skill_name": "update-androidsdk-packages", + "evals": [ + { + "id": 1, + "prompt": "Google just published build-tools 37.0.1 and platform-tools 37.0.2 as stable. Can you update src/androidsdk/androidsdk.targets and Configuration.props to pick these up, including recomputing all the SHA-256 hashes for every host?", + "expected_output": "XABuildToolsVersion/XABuildToolsFolder and XAPlatformToolsVersion bumped to the new stable revisions, with XABuildToolsHashMacOS/Linux/Windows and XAPlatformToolsHashMacOS/Linux/Windows recomputed from freshly downloaded archives (not guessed). NDK properties and the _PlatformPackage item group are untouched. androidsdk.csproj still builds (after building the BootstrapTasks project first).", + "assertions": [] + }, + { + "id": 2, + "prompt": "please update our android sdk pins to whatever's current and stable on google's repository manifest right now (cmdline-tools, cmake, emulator, build-tools, platform-tools) and refresh the hashes. also NDK r29 just went stable, grab that too while you're in there", + "expected_output": "Every SDK tool family (cmdline-tools, cmake, emulator, build-tools, platform-tools) is refreshed to Google's current stable revisions with recomputed SHA-256 hashes across all hosts, including a macOS-arm64-specific archive where Google now publishes one. The NDK request is explicitly declined/flagged as out of scope for this skill — _XAAndroidNdkRelease, _XAAndroidNdkPkgRevision, and every XAAndroidNdkHash* property are left completely unchanged, with a clear note to the user explaining why.", + "assertions": [] + }, + { + "id": 3, + "prompt": "Android platform 37.1 just came out on the SDK manifest — go ahead and add it to our androidsdk build alongside what we already support, and also bump the existing 37.0 platform package to its latest revision if there's a newer one.", + "expected_output": "A brand new _PlatformPackage entry for API 37.1 is NOT added to androidsdk.targets. The existing platform-37.0_r0N entry IS updated in place to the newest published revision (Include + Hash) if Google has released a newer r0N for 37.0, with IsLatestStable preserved on whichever entry currently has it. The response clearly explains that adding a new platform level (37.1) is intentionally out of scope for this skill and flags it for a human decision.", + "assertions": [] + }, + { + "id": 4, + "prompt": "our CI machines are on an old command-line tools version (19.0 / 13114758_latest) and I keep seeing warnings that a newer cmdline-tools package is available with separate mac_arm64/mac_x86_64 downloads now — can you get us current, and check whether Android platform 36.1 has a newer revision published too?", + "expected_output": "CommandLineToolsFolder/CommandLineToolsVersion bumped to the current stable release, with the command-line tools _AndroidSdkPackage split into a mac_x86_64 item (gated on _IsArm64Apple != 'true') and a new mac_arm64 item (gated on _IsArm64Apple == 'true') backed by a new XACmdlineToolsHashMacOSArm64 property, following the existing emulator/system-image pattern for _IsArm64Apple. The existing platform-36.1_r01 _PlatformPackage entry is checked against the manifest and its revision/hash updated in place if a newer one exists (no new API level added). Validation runs the BootstrapTasks build before the androidsdk.csproj build.", + "assertions": [] + } + ] +} diff --git a/.github/skills/update-androidsdk-packages/references/package-catalog.md b/.github/skills/update-androidsdk-packages/references/package-catalog.md new file mode 100644 index 00000000000..0b31b232d02 --- /dev/null +++ b/.github/skills/update-androidsdk-packages/references/package-catalog.md @@ -0,0 +1,40 @@ +# Android SDK package catalog reference + +Snapshot of the package families this skill manages, as of the update performed in PR #12371. +Use this as a map, not gospel — always re-check `Configuration.props` and +`src/androidsdk/androidsdk.targets` for the current values before editing, since this file will +drift as the skill is used. + +| Family | Manifest `path` prefix | Version/revision property | Hash properties | `androidsdk.targets` location | +|---|---|---|---|---| +| build-tools | `build-tools` | `XABuildToolsVersion`, `XABuildToolsFolder` | `XABuildToolsHashMacOS/Linux/Windows` | `build-tools_r$(XABuildToolsVersion)_{macosx,linux,windows}.zip` | +| platform-tools | `platform-tools` | `XAPlatformToolsVersion` | `XAPlatformToolsHashMacOS/Linux/Windows` | `platform-tools_r$(XAPlatformToolsVersion)-{darwin,linux,win}.zip` | +| cmdline-tools | `cmdline-tools` | `CommandLineToolsFolder`, `CommandLineToolsVersion` | `XACmdlineToolsHashMacOS`, `XACmdlineToolsHashMacOSArm64`, `XACmdlineToolsHashLinux/Windows` | `commandlinetools-{mac_x86_64,mac_arm64,linux,win}-$(CommandLineToolsVersion).zip` — macOS is arch-split; other hosts are one zip | +| cmake | `cmake;` | `AndroidCmakeVersion` | `XACmakeHashMacOS/Linux/Windows` | `cmake-$(AndroidCmakeVersion)-{darwin,linux,windows}.zip` | +| emulator | `emulator` | `EmulatorVersion`, `EmulatorPkgRevision` | `XAEmulatorHashMacOSx64`, `XAEmulatorHashMacOSArm64`, `XAEmulatorHashLinux/Windows` | `emulator-{darwin_x64,darwin_aarch64,linux_x64,windows_x64}-$(EmulatorVersion).zip`; also drives a synthesized `package.xml` via `package.xml.in` | +| API 29 system image | `sys-img/android` manifest, `path="system-images;android-29;default;{x86_64,arm64-v8a}"` | (fixed `x86_64-29_r08*`/`arm64-v8a-29_r08` filenames — check manifest for a newer `rNN` if refreshing) | `XASystemImageHashMacOSx64/MacOSArm64/Linux/Windows` | `{x86_64,arm64-v8a}-29_r08{-darwin,-linux,-windows,}.zip` under `sys-img/android/` | +| m2repository | `extras;android;m2repository` | (embedded in filename, e.g. `_r47`) | `XAAndroidM2RepositoryHash` | `android_m2repository_r47.zip`, host-agnostic | +| docs | `docs` | (embedded in filename, e.g. `-24_r01`) | `XAAndroidDocsHash` | `docs-24_r01.zip`, host-agnostic | +| sources | `sources;android-NN` (tracks the latest stable platform) | (embedded in filename) | `XAAndroidSourcesHash` | `source-_r0M.zip`, `Destination` embeds the API level too | +| platform APIs | `platforms;android-NN` | n/a — `_PlatformPackage` item's `Include` *is* the version string | `Hash` metadata per `_PlatformPackage` item | `_PlatformPackage` item group near the top of the file; one `IsLatestStable="true"` entry drives default install + the sources package above | +| **Android NDK — OUT OF SCOPE** | `ndk` | `_XAAndroidNdkRelease`, `_XAAndroidNdkPkgRevision` | `XAAndroidNdkHashMacOS/Linux/Windows` | `android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip` — **never edit as part of this skill** | + +## Notes on Apple Silicon archives + +`_IsArm64Apple` (computed in `Configuration.props` from `RuntimeInformation.ProcessArchitecture` +on a Darwin host) is the existing gate used to pick an arm64-specific archive when one exists +(emulator, system image, and — as of PR #12371 — cmdline-tools). When Google adds an arm64-specific +archive for a package family that didn't have one before, mirror that same conditional pattern: +an `'$(_IsArm64Apple)' != 'true'` item for the existing x86_64/generic macOS archive, and a new +`'$(_IsArm64Apple)' == 'true'` item pointing at the arm64 archive and a new `*HashMacOSArm64` +property. Don't add an arm64-specific branch speculatively for families where Google still ships +one universal/x86_64-only macOS archive. + +## Notes on platform extension levels + +Some platform API levels ship as a numbered "extension" (e.g. `platform-34-ext12_r01`) rather than +a bare `platform-NN_rMM`. The extension number tracks Google's Extension SDK program, independent +of the base API level's own revision counter. When refreshing an API level that already uses an +extension suffix, look up the current extension level and revision for that exact API in the +manifest (`dotnet run .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs -- --path "platforms;android-34"`) rather than assuming the extension +number increments in lockstep with anything else in the catalog. diff --git a/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs new file mode 100644 index 00000000000..9894ae69ba9 --- /dev/null +++ b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs @@ -0,0 +1,170 @@ +#!/usr/bin/env dotnet +// Helper for the update-androidsdk-packages skill. +// +// Fetches Google's Android SDK repository manifests and prints the +// / entries whose `path` contains a given +// substring, sorted by revision (newest first) so the current stable +// release is easy to spot. Read-only research tooling: it never edits repo +// files. Use it to answer "what is the current stable revision/URL/SHA-256 +// for package X" before hand-editing Configuration.props / androidsdk.targets. +// +// Usage: +// dotnet run fetch_repo_package.cs -- --path build-tools +// dotnet run fetch_repo_package.cs -- --path "platforms;android-37" --archives +// dotnet run fetch_repo_package.cs -- --path emulator --archives --manifest https://dl.google.com/android/repository/sys-img/android/sys-img2-3.xml +// +// Notes: +// - Google's channelRef in repository2-3.xml is NOT a reliable stable/preview +// signal by itself (some legitimately-stable packages carry a non-zero +// channel id, and freshly-promoted stable packages can briefly still show +// old channel numbers). This tool instead flags a package as +// "preview-looking" when its element is set (nonzero) +// or its path/display-name contains an obvious marker (rc, alpha, beta, +// canary, preview) matched on word boundaries so e.g. "sources" isn't +// mistaken for "rc". It prints the channel id alongside the revision so a +// human/agent can make the final call. Pass --all to see every match, +// preview-looking or not. +// - Only reads data. It does not download archives or compute SHA-256; use +// sha256_of_url.cs for that once you've picked the exact archive URL +// (Google's manifests only publish SHA-1). + +using System.Text.RegularExpressions; +using System.Xml.Linq; + +const string DefaultManifest = "https://dl.google.com/android/repository/repository2-3.xml"; +// Word-boundary matches so e.g. "sources;android-35" (contains "rc" inside +// "Sources") isn't mistaken for a release-candidate marker. +var previewMarkers = new Regex(@"\b(rc\d*|alpha\d*|beta\d*|canary|preview)\b", RegexOptions.IgnoreCase); + +string? path = null; +string manifest = DefaultManifest; +bool showAll = false; +bool showArchives = false; + +for (int i = 0; i < args.Length; i++) { + switch (args[i]) { + case "--path": path = ++i < args.Length ? args[i] : null; break; + case "--manifest": manifest = ++i < args.Length ? args[i] : manifest; break; + case "--all": showAll = true; break; + case "--archives": showArchives = true; break; + case "--help": case "-h": + PrintUsage(); + return 0; + } +} + +if (string.IsNullOrEmpty(path)) { + PrintUsage(); + return 1; +} + +XDocument doc; +try { + using var http = new HttpClient(); + http.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-android-skill/1.0"); + var xml = await http.GetStringAsync(manifest); + doc = XDocument.Parse(xml); +} catch (Exception ex) { + Console.Error.WriteLine($"error: failed to fetch/parse {manifest}: {ex.Message}"); + return 1; +} + +var matches = new List(); +foreach (var pkg in doc.Descendants().Where(e => e.Name.LocalName is "remotePackage" or "localPackage")) { + var pkgPath = (string?)pkg.Attribute("path") ?? ""; + if (!pkgPath.Contains(path, StringComparison.Ordinal)) + continue; + + var revisionElem = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "revision"); + var (revisionText, revisionKey) = ParseRevision(revisionElem); + bool hasPreviewRevision = revisionElem?.Elements().FirstOrDefault(e => e.Name.LocalName == "preview") is { } previewElem + && int.TryParse(previewElem.Value, out var previewNum) && previewNum != 0; + var displayName = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "display-name")?.Value ?? ""; + // is a direct child of /, not of . + var channelRef = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "channelRef")?.Attribute("ref")?.Value ?? ""; + bool preview = hasPreviewRevision || previewMarkers.IsMatch($"{pkgPath} {displayName}"); + if (preview && !showAll) + continue; + + var archives = showArchives ? CollectArchives(pkg, manifest) : new List(); + matches.Add(new PackageMatch(pkgPath, revisionText, revisionKey, displayName, channelRef, preview, archives)); +} + +if (matches.Count == 0) { + Console.Error.WriteLine($"No packages matched --path '{path}' in {manifest}"); + return 1; +} + +foreach (var m in matches.OrderByDescending(m => m.RevisionKey)) { + string flag = m.PreviewGuess ? " [PREVIEW-LOOKING]" : ""; + Console.WriteLine($"{m.Path} rev={m.Revision} channel={m.ChannelRef}{flag} {m.DisplayName}"); + foreach (var a in m.Archives) { + string tag = string.Join("/", new[] { a.HostOs, a.HostArch }.Where(s => !string.IsNullOrEmpty(s))); + if (tag.Length == 0) + tag = "generic"; + Console.WriteLine($" [{tag}] {a.Url} sha1={a.Sha1} size={a.Size}"); + } +} + +return 0; + +static void PrintUsage() +{ + Console.Error.WriteLine("usage: dotnet run fetch_repo_package.cs -- --path [--manifest ] [--all] [--archives]"); + Console.Error.WriteLine($" --path required substring to match against each package's `path` attribute (e.g. 'build-tools', 'platforms;android-37', 'emulator')"); + Console.Error.WriteLine($" --manifest manifest URL (default: {DefaultManifest}); use a sys-img*.xml URL for system images"); + Console.Error.WriteLine(" --all show every match, including ones that look like previews (default: stable-looking only)"); + Console.Error.WriteLine(" --archives print archive URLs + SHA-1 + size for each match (SHA-1 only; recompute SHA-256 from the actual download)"); +} + +static (string Text, (int, int, int) Key) ParseRevision(XElement? revisionElem) +{ + if (revisionElem is null) + return ("", (0, 0, 0)); + + int Int(string name) { + var s = revisionElem.Elements().FirstOrDefault(e => e.Name.LocalName == name)?.Value; + return int.TryParse(s, out var v) ? v : 0; + } + + string? Str(string name) => revisionElem.Elements().FirstOrDefault(e => e.Name.LocalName == name)?.Value; + + int major = Int("major"); + string? minor = Str("minor"); + string? micro = Str("micro"); + var parts = new List { major.ToString() }; + if (!string.IsNullOrEmpty(minor)) parts.Add(minor); + if (!string.IsNullOrEmpty(micro)) parts.Add(micro); + return (string.Join(".", parts), (major, Int("minor"), Int("micro"))); +} + +static List CollectArchives(XElement pkg, string manifestUrl) +{ + var result = new List(); + Uri? baseUri = Uri.TryCreate(manifestUrl, UriKind.Absolute, out var u) ? u : null; + foreach (var archivesElem in pkg.Elements().Where(e => e.Name.LocalName == "archives")) { + foreach (var archive in archivesElem.Elements().Where(e => e.Name.LocalName == "archive")) { + var complete = archive.Elements().FirstOrDefault(e => e.Name.LocalName == "complete"); + if (complete is null) + continue; + string hostOs = archive.Elements().FirstOrDefault(e => e.Name.LocalName == "host-os")?.Value ?? ""; + string hostArch = archive.Elements().FirstOrDefault(e => e.Name.LocalName == "host-arch")?.Value ?? ""; + string rawUrl = complete.Elements().FirstOrDefault(e => e.Name.LocalName == "url")?.Value ?? ""; + // Archive values in the manifest are relative to the manifest's own + // location (e.g. "build-tools_r30.0.3-linux.zip"), not absolute. Resolve + // against the manifest URI so the printed URL can be fed straight into + // sha256_of_url.cs / DownloadFile without the caller having to guess the + // base path. + string url = rawUrl; + if (baseUri is not null && Uri.TryCreate(baseUri, rawUrl, out var resolved)) + url = resolved.AbsoluteUri; + string sha1 = complete.Elements().FirstOrDefault(e => e.Name.LocalName == "checksum")?.Value ?? ""; + string size = complete.Elements().FirstOrDefault(e => e.Name.LocalName == "size")?.Value ?? ""; + result.Add(new ArchiveInfo(hostOs, hostArch, url, sha1, size)); + } + } + return result; +} + +record PackageMatch(string Path, string Revision, (int, int, int) RevisionKey, string DisplayName, string ChannelRef, bool PreviewGuess, List Archives); +record ArchiveInfo(string HostOs, string HostArch, string Url, string Sha1, string Size); diff --git a/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs new file mode 100644 index 00000000000..a8702d2d95b --- /dev/null +++ b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs @@ -0,0 +1,80 @@ +#!/usr/bin/env dotnet +// Download a URL into a scratch temp file (NOT the Android archive cache at +// $HOME/android-archives) and print its SHA-256 in the uppercase hex format +// Configuration.props expects. Use this only when Google's manifest doesn't +// publish an authoritative SHA-256 for an archive you need to pin (Google's +// repository manifests only carry SHA-1). Never hand-guess a hash. +// +// Usage: +// dotnet run sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip +// +// Deletes the downloaded file after hashing unless --keep is passed, so it +// never leaves stray files behind for the diff/cache-cleanliness check. + +using System.Security.Cryptography; + +string? url = null; +bool keep = false; + +foreach (var a in args) { + if (a == "--keep") + keep = true; + else if (!a.StartsWith("--", StringComparison.Ordinal)) + url = a; +} + +if (string.IsNullOrEmpty(url)) { + Console.Error.WriteLine("usage: dotnet run sha256_of_url.cs -- [--keep]"); + return 1; +} + +string? tmpPath = null; + +try { + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)) { + Console.Error.WriteLine($"error: '{url}' is not a valid absolute http(s) URL"); + return 1; + } + + tmpPath = Path.Combine(Path.GetTempPath(), $"androidsdk-skill-{Guid.NewGuid():N}{Path.GetExtension(uri.AbsolutePath)}"); + + using var http = new HttpClient(); + http.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-android-skill/1.0"); + http.Timeout = TimeSpan.FromMinutes(10); + + long size = 0; + using (var sha256 = SHA256.Create()) + using (var response = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)) { + response.EnsureSuccessStatusCode(); + await using var httpStream = await response.Content.ReadAsStreamAsync(); + await using var fileStream = File.Create(tmpPath); + await using var cryptoStream = new CryptoStream(fileStream, sha256, CryptoStreamMode.Write); + await httpStream.CopyToAsync(cryptoStream); + await cryptoStream.FlushFinalBlockAsync(); + size = new FileInfo(tmpPath).Length; + + string digest = Convert.ToHexString(sha256.Hash!); + Console.WriteLine($"url: {url}"); + Console.WriteLine($"size: {size} bytes"); + Console.WriteLine($"sha256: {digest}"); + } + + if (keep) + Console.WriteLine($"kept at: {tmpPath}"); + + return 0; +} catch (Exception ex) { + Console.Error.WriteLine($"error: failed to download/hash {url}: {ex.Message}"); + return 1; +} finally { + // Cleanup failures (e.g. a transient file lock) must not mask the real + // download/hash outcome above, so report them separately instead of + // letting an exception escape the finally block. + if (!keep && tmpPath is not null && File.Exists(tmpPath)) { + try { + File.Delete(tmpPath); + } catch (Exception cleanupEx) { + Console.Error.WriteLine($"warning: failed to delete temp file '{tmpPath}': {cleanupEx.Message}"); + } + } +} diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml new file mode 100644 index 00000000000..479048831e1 --- /dev/null +++ b/.github/workflows/skill-runner.lock.yml @@ -0,0 +1,1807 @@ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"49a676d9bfda6b36487f4de86d3ddc29cb660a28e459a7d07bad471e974e9a1e","body_hash":"51b603bd3c1fb3cc71e30049940ab2fb6d54de7914121be5744f4208f2b451d0","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} +# This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Weekly (or on-demand) runner that executes a selectable repository Copilot skill end to end, opens a PR for validated changes, and always reports outcome/errors on a tracking issue +# +# Resolved workflow manifest: +# Imports: +# - shared/pat_pool.md +# +# Secrets used: +# - COPILOT_PAT_0 +# - COPILOT_PAT_1 +# - COPILOT_PAT_2 +# - COPILOT_PAT_3 +# - COPILOT_PAT_4 +# - COPILOT_PAT_5 +# - COPILOT_PAT_6 +# - COPILOT_PAT_7 +# - COPILOT_PAT_8 +# - COPILOT_PAT_9 +# - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) +# - actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 +# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff +# - ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627 +# - ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f +# - ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 +# - ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e + +name: "Skill Runner" +on: + pull_request: + paths: + - .github/workflows/skill-runner.__never_matches__ + schedule: + - cron: "18 3 * * 1" # Friendly format: weekly on monday around 03:00 (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + skill: + description: Skill to run (leave blank for random) + options: + - "" + - update-androidsdk-packages + required: false + type: choice + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" + cancel-in-progress: true + +run-name: "Skill Runner" + +jobs: + activation: + needs: + - pat_pool + - pre_activation + if: > + needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || + github.event.pull_request.stack.position == github.event.pull_request.stack.size)) + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "gpt-5.6-sol" + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AGENT_VERSION: "1.0.79" + GH_AW_INFO_CLI_VERSION: "v0.86.2" + GH_AW_INFO_WORKFLOW_NAME: "Skill Runner" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github","dotnet","java"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_INFO_AGENT_RUNTIME: "" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Check for OAuth tokens + id: check-oauth-tokens + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh" + 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') + }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .claude + .codex + .gemini + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "skill-runner.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.86.2" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.gradle-enterprise.cloud,*.vsblob.vsassets.io,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" + - name: Create prompt with built-in context + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"file\":\"safe_outputs_create_pull_request.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"file\":\"mcp_cli_tools_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"cli_proxy_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"}]}" + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_PROMPT_CONTENT_0000: "\n" + GH_AW_PROMPT_CONTENT_0001: "\nTools: create_issue, create_pull_request, missing_tool, missing_data, noop\n" + GH_AW_PROMPT_CONTENT_0002: "\n" + GH_AW_PROMPT_CONTENT_0003: "\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n- **checkouts**: The following repositories have been checked out and are available in the workspace:\n - repo `__GH_AW_GITHUB_REPOSITORY__` → `$GITHUB_WORKSPACE` (cwd) [full history, all branches available as remote-tracking refs]\n - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: [\"refs/pulls/open/*\"]` for all open PR refs, or `fetch: [\"main\", \"feature/my-branch\"]` for specific branches).\n - **Warning: No git credentials are available to the agent.** Credentials are\n intentionally removed after the checkout step for security. This means any git\n operation that needs to authenticate to the remote will fail. In private repositories, that includes:\n - `git fetch`, `git pull`, `git clone`, and `git push` (direct push, not via safe-output tools)\n - Checking out or switching to a remote branch that is not already fetched\n - Deepening a shallow clone (`git fetch --unshallow`)\n - On-demand blob fetches in partial/blobless clones (operations on files not in the initial checkout)\n Do NOT attempt to configure credentials, run `git credential fill`, or modify `.gitconfig` —\n authentication will not succeed. If you encounter credential prompts or authentication errors,\n stop immediately and report the limitation rather than spending turns trying to work around it.\n\n\n" + GH_AW_PROMPT_CONTENT_0004: "\n" + GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/skill-runner.md}}\n" + with: + script: | + const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs'); + await main(core); + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Stage prompt files for artifact upload + run: | + mkdir -p /tmp/gh-aw/aw-prompts + cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/ + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: + - activation + - pat_pool + runs-on: ubuntu-latest + environment: copilot-pat-pool + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_WORKFLOW_ID_SANITIZED: skillrunner + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} + max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }} + missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 + with: + dotnet-version: '8.0' + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - env: + INPUT_SKILL: ${{ inputs.skill }} + name: Select skill and bootstrap prerequisites + run: "mkdir -p /tmp/gh-aw/agent\nSKILL_ROOT=\".github/skills\"\n# The set of skills this workflow is allowed to run unattended. Keep this\n# in sync with the workflow_dispatch `skill` options list above.\nELIGIBLE_SKILLS=(\"update-androidsdk-packages\")\n\nif [ -n \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"$INPUT_SKILL\"\nelse\n # No explicit selection (scheduled run, or manual dispatch left blank):\n # pick uniformly at random from the eligible skills, same as\n # nightly-fix-finder does for its scan scripts. With only one skill\n # registered today this always picks it; once more are added, each\n # unselected run picks one at random.\n COUNT=${#ELIGIBLE_SKILLS[@]}\n SKILL_NAME=\"${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}\"\nfi\n\nSKILL_PATH=\"$SKILL_ROOT/$SKILL_NAME/SKILL.md\"\nif [ ! -f \"$SKILL_PATH\" ]; then\n echo \"❌ Requested skill not found: $SKILL_PATH\" >&2\n exit 1\nfi\necho \"$SKILL_NAME\" > /tmp/gh-aw/agent/selected-skill.txt\necho \"✅ Selected skill: $SKILL_NAME ($SKILL_PATH)\"\n\n# Skill-specific prerequisite bootstrap. androidsdk.csproj requires the\n# BootstrapTasks assembly to evaluate at all, so build it up front whenever\n# that skill is selected. Add an `elif` here for future skills that need\n# their own bootstrap step, rather than special-casing it in the prompt.\nif [ \"$SKILL_NAME\" = \"update-androidsdk-packages\" ]; then\n dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal\nfi" + + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install ripgrep + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_ripgrep.sh" + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" + env: + GH_HOST: github.com + GH_AW_COMPILED_VERSION: v0.86.2 + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 --rootless + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + GH_AW_GITHUB_MIN_INTEGRITY: 'none' + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .github" + GH_AW_AGENT_FILES: "AGENTS.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627 ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f82b94499c899e1c_EOF' + {"create_issue":{"close_older_issues":true,"expires":720,"labels":["automated","skill-runner"],"max":1,"title_prefix":"[skill-runner] "},"create_pull_request":{"allowed_base_branches":["main"],"allowed_files":["Configuration.props","src/androidsdk/androidsdk.targets"],"auto_close_issue":false,"draft":false,"fallback_as_issue":false,"labels":["automated","skill-runner"],"max":1,"max_patch_files":5,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md"],"protected_files_policy":"request_review","title_prefix":"[skill-runner] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_f82b94499c899e1c_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[skill-runner] \". Labels [\"automated\" \"skill-runner\"] will be automatically added.", + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[skill-runner] \". Labels [\"automated\" \"skill-runner\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000, + "minLength": 20 + }, + "fields": { + "type": "array" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "create_pull_request": { + "defaultMax": 1, + "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} + GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="awmg-mcpg" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.9' + + mkdir -p "$HOME/.copilot" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_8ad252ebec65d038_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "safeoutputs": { + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_SHA": "\${GITHUB_SHA}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ], + "sink-visibility": "${GH_AW_SINK_VISIBILITY}" + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 + } + } + GH_AW_MCP_CONFIG_8ad252ebec65d038_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Start CLI Proxy + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_HOST: ${{ env.GH_HOST }} + GITHUB_HOST: ${{ env.GITHUB_HOST }} + GITHUB_ENTERPRISE_HOST: ${{ env.GITHUB_ENTERPRISE_HOST }} + GITHUB_GRAPHQL_URL: ${{ env.GITHUB_GRAPHQL_URL }} + GITHUB_COPILOT_BASE_URL: ${{ env.GITHUB_COPILOT_BASE_URL }} + GH_AW_NETWORK_ISOLATION: 'true' + CLI_PROXY_POLICY: '{"allow-only":{"min-integrity":"none","repos":"${{ steps.determine-automatic-lockdown.outputs.repos }}"}}' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.4.9' + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 120 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" + GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)" + if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then + echo "GitHub Copilot CLI executable not found on PATH after installation" >&2 + exit 127 + fi + GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot" + mkdir -p "${RUNNER_TEMP}/gh-aw/bin" + if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then + cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN" + fi + chmod 755 "$GH_AW_COPILOT_BIN" + + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC2016 + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.gradle-enterprise.cloud","*.vsblob.vsassets.io","adoptium.net","api.adoptium.net","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.foojay.io","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.apache.org","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","cdn.azul.com","central.sonatype.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","develocity.apache.org","dist.nuget.org","dl.google.com","dlcdn.apache.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","download.eclipse.org","download.java.net","download.oracle.com","downloads.gradle-dn.com","ge.spockframework.org","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","gradle.org","host.docker.internal","jcenter.bintray.com","jdk.java.net","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","maven-central.storage-download.googleapis.com","maven.apache.org","maven.google.com","maven.oracle.com","maven.pkg.github.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","patchdiff.githubusercontent.com","pkgs.dev.azure.com","plugins-artifacts.gradle.org","plugins.gradle.org","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","repo.gradle.org","repo.grails.org","repo.maven.apache.org","repo.spring.io","repo1.maven.org","repository.apache.org","s.symcb.com","s.symcd.com","scans-in.gradle.com","security.ubuntu.com","services.gradle.org","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.java.com","www.microsoft.com"],"isolation":true,"topologyAttach":["awmg-mcpg","awmg-cli-proxy"]},"apiProxy":{"enabled":true,"maxRuns":500,"maxCacheMisses":5,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.5","gpt-5.6","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"auto":["copilot/auto","large"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex","kimi"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"detection":["small"],"evals":["small"],"fable":["copilot/*fable*","anthropic/*fable*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-3.6-flash":["copilot/gemini-3.6*flash*","google/gemini-3.6*flash*","gemini/gemini-3.6*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-omni":["copilot/gemini-omni*","google/gemini-omni*","gemini/gemini-omni*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.1":["copilot/gpt-5.1*","openai/gpt-5.1*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"gpt-5.6":["copilot/gpt-5.6*","openai/gpt-5.6*"],"grok":["copilot/*grok*","openai/*grok*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"image-generation":["copilot/gpt-image*","openai/gpt-image*","openai/chatgpt-image*","copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","google/imagen*"],"kimi":["copilot/kimi*","openai/kimi*"],"kiwi":["copilot/kiwi*","openai/kiwi*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"lyria":["google/lyria*","gemini/lyria*","copilot/lyria*"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mai-code-1-flash-picker":["copilot/MAI-Code-1-Flash-picker*","copilot/mai-code-1-flash-picker*","openai/MAI-Code-1-Flash-picker*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"raptor-mini":["copilot/raptor*","openai/raptor*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-5*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*","anthropic/*sonnet-5*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"veo":["google/veo*","gemini/veo*"],"vision":["copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},"logging":{"proxyLogsDir":"/tmp/gh-aw/sandbox/firewall/logs","auditDir":"/tmp/gh-aw/sandbox/firewall/audit"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2016,SC2086 + awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + 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') + }} + COPILOT_MODEL: gpt-5.6-sol + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_TIMEOUT_MINUTES: 120 + GH_AW_VERSION: v0.86.2 + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Stop CLI Proxy + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/stop_cli_proxy.sh" + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_PAT_0,COPILOT_PAT_1,COPILOT_PAT_2,COPILOT_PAT_3,COPILOT_PAT_4,COPILOT_PAT_5,COPILOT_PAT_6,COPILOT_PAT_7,COPILOT_PAT_8,COPILOT_PAT_9,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.gradle-enterprise.cloud,*.vsblob.vsassets.io,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - pat_pool + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + environment: copilot-pat-pool + permissions: + actions: read + contents: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-skill-runner" + cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download Safe Outputs Items Manifest + id: download-safe-outputs-manifest + if: always() + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: safe-outputs-items + path: /tmp/gh-aw/ + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh" + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/evals.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "skill-runner" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "false" + GH_AW_MISSING_TOOL_TITLE_PREFIX: "[missing tool]" + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "false" + GH_AW_REPORT_INCOMPLETE_TITLE_PREFIX: "[incomplete]" + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "skill-runner" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: "-1" + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }} + GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }} + GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }} + GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "120" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Report failed jobs + id: report_failed_jobs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_REPORT_FAILED_JOBS: "true" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_failed_jobs.cjs'); + await main(); + + detection: + needs: + - activation + - agent + - pat_pool + if: always() && needs.agent.result != 'skipped' + runs-on: ubuntu-latest + environment: copilot-pat-pool + permissions: + contents: read + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f "$HOME/.copilot/mcp-config.json" + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh" + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Skill Runner" + WORKFLOW_DESCRIPTION: "Weekly (or on-demand) runner that executes a selectable repository Copilot skill end to end, opens a PR for validated changes, and always reports outcome/errors on a tracking issue" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + rm -f /tmp/gh-aw/step-summary.md + touch /tmp/gh-aw/step-summary.md + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install ripgrep + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_ripgrep.sh" + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" + env: + GH_HOST: github.com + GH_AW_COMPILED_VERSION: v0.86.2 + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)" + if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then + echo "GitHub Copilot CLI executable not found on PATH after installation" >&2 + exit 127 + fi + GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot" + mkdir -p "${RUNNER_TEMP}/gh-aw/bin" + if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then + cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN" + fi + chmod 755 "$GH_AW_COPILOT_BIN" + + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + _GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2016,SC2086 + awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + GITHUB_STEP_SUMMARY: /tmp/gh-aw/step-summary.md + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + 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') + }} + COPILOT_MODEL: gpt-5.6-sol + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.86.2 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Echo detection step summary + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + run: | + if [ -s /tmp/gh-aw/step-summary.md ]; then + cat /tmp/gh-aw/step-summary.md + fi + - name: Render detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/render_detection_log.cjs'); + await main(); + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + pat_pool: + needs: pre_activation + runs-on: ubuntu-slim + environment: copilot-pat-pool + outputs: + pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Select Copilot token from pool + id: select-pat-number + run: | + # Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9. + PAT_NUMBERS=() + POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖) + + for i in $(seq 0 9); do + var="COPILOT_PAT_${i}" + val="${!var}" + if [ -n "$val" ]; then + PAT_NUMBERS+=(${i}) + POOL_INDICATORS[${i}]="🟪" + fi + done + + # If none of the entries in the pool have values, emit a warning + # and do not set an output value. The consumer can fall back to + # using COPILOT_GITHUB_TOKEN. + if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then + warning_message="::warning::None of the PAT pool entries had values " + warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)" + echo "$warning_message" + exit 0 + fi + + # Select a random index using the seed if specified + if [ -n "$RANDOM_SEED" ]; then + RANDOM=$RANDOM_SEED + fi + + PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) + PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" + POOL_INDICATORS[${PAT_NUMBER}]="✅" + + echo "Pool size: ${#PAT_NUMBERS[@]}" + echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})" + + # Emit a markdown table of the pool entries to the step summary + echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY" + echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY" + (IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY" + + # Set the PAT number as the output + echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" + env: + COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} + COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} + COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} + COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} + COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} + COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} + COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} + COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} + COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} + COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} + RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }} + shell: bash + + pre_activation: + if: > + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || + github.event.pull_request.stack.position == github.event.pull_request.stack.size) + runs-on: ubuntu-slim + environment: copilot-pat-pool + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + environment: copilot-pat-pool + permissions: + contents: write + issues: write + pull-requests: write + timeout-minutes: 45 + env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/skill-runner" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "gpt-5.6-sol" + GH_AW_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_WORKFLOW_ID: "skill-runner" + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/skill-runner.md" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} + created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} + process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }} + process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }} + process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }} + process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }} + process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }} + process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }} + process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@6aab9e5b5c91c615506061f09bedd81a23babe3c # v0.86.2 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Skill Runner" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/skill-runner.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.79" + GH_AW_INFO_AWF_VERSION: "v0.27.44" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: true + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + - name: Configure Git credentials + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.gradle-enterprise.cloud,*.vsblob.vsassets.io,adoptium.net,api.adoptium.net,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.foojay.io,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.apache.org,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,cdn.azul.com,central.sonatype.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,develocity.apache.org,dist.nuget.org,dl.google.com,dlcdn.apache.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,download.eclipse.org,download.java.net,download.oracle.com,downloads.gradle-dn.com,ge.spockframework.org,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,gradle.org,host.docker.internal,jcenter.bintray.com,jdk.java.net,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,maven-central.storage-download.googleapis.com,maven.apache.org,maven.google.com,maven.oracle.com,maven.pkg.github.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,patchdiff.githubusercontent.com,pkgs.dev.azure.com,plugins-artifacts.gradle.org,plugins.gradle.org,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,repo.gradle.org,repo.grails.org,repo.maven.apache.org,repo.spring.io,repo1.maven.org,repository.apache.org,s.symcb.com,s.symcd.com,scans-in.gradle.com,security.ubuntu.com,services.gradle.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.java.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":720,\"labels\":[\"automated\",\"skill-runner\"],\"max\":1,\"title_prefix\":\"[skill-runner] \"},\"create_pull_request\":{\"allowed_base_branches\":[\"main\"],\"allowed_files\":[\"Configuration.props\",\"src/androidsdk/androidsdk.targets\"],\"auto_close_issue\":false,\"draft\":false,\"fallback_as_issue\":false,\"labels\":[\"automated\",\"skill-runner\"],\"max\":1,\"max_patch_files\":5,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[skill-runner] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/process_safe_outputs.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md new file mode 100644 index 00000000000..8307d2d1700 --- /dev/null +++ b/.github/workflows/skill-runner.md @@ -0,0 +1,313 @@ +--- +on: + pull_request: + paths: + # Sentinel path that never matches. Keeping a pull_request trigger here is + # required so gh-aw emits a pre_activation job (which shared/pat_pool.md's + # pat_pool job depends on), but actually running on PRs would fail because + # the copilot-pat-pool environment rejects PR refs via protection rules. + - .github/workflows/skill-runner.__never_matches__ + schedule: + - cron: weekly on monday around 03:00 + workflow_dispatch: + inputs: + skill: + description: Skill to run (leave blank for random) + options: + - "" + - "update-androidsdk-packages" + required: false + type: choice +permissions: + contents: read + issues: read + pull-requests: read +# ############################################################### +# 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. +# ############################################################### +# +# The PAT pool authenticates Copilot requests only. Repository writes use the +# workflow GITHUB_TOKEN, so generated commits and PRs are authored by +# github-actions[bot]. +imports: + - uses: shared/pat_pool.md + with: + environment: copilot-pat-pool + +environment: copilot-pat-pool +checkout: + - fetch-depth: 0 +jobs: + conclusion: + permissions: + issues: write +network: + allowed: + - defaults + - github + - dotnet + - java +safe-outputs: + github-token: ${{ secrets.GITHUB_TOKEN }} + create-pull-request: + allowed-base-branches: + - main + allowed-files: + - Configuration.props + - src/androidsdk/androidsdk.targets + auto-close-issue: false + draft: false + fallback-as-issue: false + labels: + - automated + - skill-runner + max-patch-files: 5 + title-prefix: "[skill-runner] " + create-issue: + title-prefix: "[skill-runner] " + labels: + - automated + - skill-runner + close-older-issues: true + expires: 30 + missing-data: + create-issue: false + missing-tool: + create-issue: false + noop: + report-as-issue: true + report-incomplete: + create-issue: false + report-failure-as-issue: true +steps: +- env: + INPUT_SKILL: ${{ inputs.skill }} + name: Select skill and bootstrap prerequisites + run: | + mkdir -p /tmp/gh-aw/agent + SKILL_ROOT=".github/skills" + # The set of skills this workflow is allowed to run unattended. Keep this + # in sync with the workflow_dispatch `skill` options list above. + ELIGIBLE_SKILLS=("update-androidsdk-packages") + + if [ -n "$INPUT_SKILL" ]; then + SKILL_NAME="$INPUT_SKILL" + else + # No explicit selection (scheduled run, or manual dispatch left blank): + # pick uniformly at random from the eligible skills, same as + # nightly-fix-finder does for its scan scripts. With only one skill + # registered today this always picks it; once more are added, each + # unselected run picks one at random. + COUNT=${#ELIGIBLE_SKILLS[@]} + SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" + fi + + SKILL_PATH="$SKILL_ROOT/$SKILL_NAME/SKILL.md" + if [ ! -f "$SKILL_PATH" ]; then + echo "❌ Requested skill not found: $SKILL_PATH" >&2 + exit 1 + fi + echo "$SKILL_NAME" > /tmp/gh-aw/agent/selected-skill.txt + echo "✅ Selected skill: $SKILL_NAME ($SKILL_PATH)" + + # Skill-specific prerequisite bootstrap. androidsdk.csproj requires the + # BootstrapTasks assembly to evaluate at all, so build it up front whenever + # that skill is selected. Add an `elif` here for future skills that need + # their own bootstrap step, rather than special-casing it in the prompt. + if [ "$SKILL_NAME" = "update-androidsdk-packages" ]; then + dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal + fi +description: Weekly (or on-demand) runner that executes a selectable repository Copilot skill end to end, opens a PR for validated changes, and always reports outcome/errors on a tracking issue +model: gpt-5.6-sol +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') + }} +max-daily-ai-credits: -1 +max-ai-credits: -1 +strict: true +timeout-minutes: 120 +tools: + edit: + bash: ["*"] + github: + github-token: ${{ secrets.GITHUB_TOKEN }} + mode: gh-proxy + min-integrity: none + toolsets: + - repos + - issues + - pull_requests + - search +--- + +# Skill Runner + +You are the Skill Runner Agent — a generic executor for repository Copilot skills that need to run +unattended on a schedule or on demand, validate their own work, open a PR when changes are warranted, +and always report the outcome (including no-ops and errors) on a tracking issue. + +This workflow is intentionally skill-agnostic. Today it only runs `update-androidsdk-packages`, but +it is designed to grow: once more than one skill is registered, an unselected run (scheduled, or +manual dispatch with the dropdown left blank) picks one at random, the same way `nightly-fix-finder` +randomly picks a scan script. Explicitly picking a skill from the `workflow_dispatch` dropdown always +runs that one. + +## Current Context + +- **Repository**: ${{ github.repository }} +- **Selected skill name**: `/tmp/gh-aw/agent/selected-skill.txt` (written by the `Select skill and + bootstrap prerequisites` step above — read this file first). It reflects whichever skill was + explicitly chosen via `workflow_dispatch`, or a random pick among the eligible skills when the run + was scheduled or dispatched without a selection. +- **Skill path**: `.github/skills//SKILL.md`. +- Any skill-specific prerequisite (for example building + `build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj`, + which `androidsdk.csproj` requires to evaluate at all) has already been run for you by that same + step, keyed off the selected skill name. + +## Mission + +Each run: + +1. Read `/tmp/gh-aw/agent/selected-skill.txt` to determine which skill to run this time. +2. Load and follow that skill's `SKILL.md` **in full, exactly as written** — it is the authoritative + process for whatever task it describes, including any hard rules it defines. Do not improvise or + substitute your own approach for what the skill documents. +3. If the skill's process results in a validated change: implement it, validate it fully per the + skill's own instructions, commit, and open exactly one PR. +4. Regardless of whether anything changed, report the outcome on the tracking issue described below. + This includes genuine no-op runs, and it includes surfacing anything the skill's own rules say must + always be reported even when nothing else changed (see the skill table below for known examples). +5. If anything fails (network error, validation failure, ambiguous data, a rule in the skill you + cannot satisfy confidently), stop, do not open a broken PR, and report the failure on the tracking + issue instead. + +## Known Skills + +Consult this table for skill-specific context that supplements (never overrides) the skill's own +`SKILL.md`. Add a row here whenever a new skill is wired into this workflow's dropdown. + +| Skill | SKILL.md | Notes for this workflow | +|---|---|---| +| `update-androidsdk-packages` | `.github/skills/update-androidsdk-packages/SKILL.md` | Refreshes stable Android SDK package pins in `Configuration.props` / `src/androidsdk/androidsdk.targets` for `androidsdk.csproj`. Hard rules: never touch the NDK; never add a new platform API level to `_PlatformPackage`, but **always** report in the tracking issue when a newer stable platform level exists upstream (e.g. platform 37.1), whether or not anything else changed. Validation must build `build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj` before `src/androidsdk/androidsdk.csproj` — already done by the bootstrap step above. | + +## Phase 1: Follow the Selected Skill + +Read the selected skill's `SKILL.md` now and execute its documented workflow end to end. Use any +scripts it bundles (for example `dotnet run *.cs` helpers) exactly as documented there rather than +reimplementing equivalent logic ad hoc. Do not deviate from any hard rule the skill defines — treat +those as non-negotiable regardless of how the task otherwise seems to be going. + +## Phase 2: Validate + +Follow the skill's own validation steps in full before committing anything. If validation cannot pass +(build failure, ambiguous data, network failure, or any other blocker), do not commit or open a PR — +go straight to Phase 4 and report the failure instead. + +## Phase 3: Commit and Open the PR (only if changes were made and validated) + +1. Commit with a concise message describing exactly what changed, per the skill's own guidance, ending + in: + + `Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>` + + The workflow configures Git as `github-actions[bot]` — keep that author identity. +2. Call `create_pull_request` exactly once. Use a short branch name and a PR body with this + structure: + + ```markdown + > AI-generated update. Produced by the `skill-runner` agentic workflow running the `` skill. + + ### Changes + + [What changed, following the selected skill's own summary format] + + ### Validation + + [Exact build/test commands run and their results] + + ### Notes + + [Anything the skill's rules require you to always report, e.g. a newer platform level found upstream] + ``` + +## Phase 4: Report on the Tracking Issue (every run, unconditionally) + +Regardless of whether a PR was opened, report the outcome via `create_issue`. Because +`close-older-issues: true` and a stable `title-prefix` are configured, this naturally supersedes the +previous run's report instead of piling up duplicate issues. + +Always include, in every report: + +- **Skill run**: which skill ran this time (from `/tmp/gh-aw/agent/selected-skill.txt`). +- **Run outcome**: one of "Changes found and PR opened (#N)", "No-op — nothing needed updating", or + "Failed — [reason]". +- **Always-report notes**: anything the selected skill's own rules require surfacing every run + regardless of outcome (see the Known Skills table above — for `update-androidsdk-packages` this is + the platform-catalog check: state explicitly whether a newer stable platform level exists upstream + beyond the highest one already in `_PlatformPackage`, or that none does). Never omit this, even on a + pure no-op or failed run where you got far enough to check. +- **Errors**, if any occurred. Include enough detail (command, error text) for a human to act on it. + +## Rules + +1. **One PR per run at most** — never open more than one PR, and only when changes were made and + validated successfully. +2. **Always report** — every run ends with a `create_issue` call summarizing the outcome, including + pure no-ops and failures. This workflow's whole purpose is to keep a human informed even when + nothing changed. +3. **Never skip a skill's always-report rules** — if the selected skill defines something that must be + surfaced every run (like a newer platform level upstream), include it in the tracking issue every + time, regardless of whether the run's trigger mentioned it. +4. **Never deviate from a skill's hard rules** — whatever the selected `SKILL.md` marks as a hard + rule or exclusion is non-negotiable. +5. **Validate before opening a PR** — do not open a PR unless the selected skill's own validation + steps pass. +6. **Respect repo conventions** — follow dotnet/android formatting, testing, and MSBuild rules + regardless of which skill is running. +7. **Stay skill-agnostic** — do not hardcode assumptions about `update-androidsdk-packages` into your + reasoning beyond what the Known Skills table documents; when a new skill is added, follow its + `SKILL.md` on its own terms. + +## Important + +You **MUST** end by calling `create_issue` exactly once (the tracking report), and additionally +`create_pull_request` exactly once if — and only if — validated changes were made this run. + +```json +{"create_issue": {"title": "Weekly scan", "body": "Skill run: update-androidsdk-packages\n\nNo-op — catalog already matches Google's current stable releases.\n\nPlatform catalog check: no newer stable platform level exists upstream beyond platform 37.0, the highest entry in _PlatformPackage."}} +``` + +## Adding a New Skill + +1. Confirm the skill has a `.github/skills//SKILL.md` that is fully self-contained and safe to + run unattended (it should validate its own work and not require a human in the loop mid-run). +2. Add the skill name to both the `workflow_dispatch` → `skill` → `options` list and the + `ELIGIBLE_SKILLS` bash array in the `Select skill and bootstrap prerequisites` step at the top of + this file, so it appears in the GitHub Actions UI dropdown and is eligible for random selection. +3. Add a row to the Known Skills table above summarizing any always-report rules, hard rules, or + prerequisite bootstrap steps a human maintaining this workflow needs to know about. +4. If the skill needs a prerequisite build/bootstrap step (like `update-androidsdk-packages` needs + `Xamarin.Android.Tools.BootstrapTasks.csproj` built first), add an `elif` branch to the `Select + skill and bootstrap prerequisites` step's shell script. +5. If the skill touches files outside `Configuration.props`/`src/androidsdk/androidsdk.targets`, + update `safe-outputs.create-pull-request.allowed-files` to include them. +6. Run `gh aw compile` to regenerate `skill-runner.lock.yml`. From 60cbcf214e08a436f4e2ea3dcc4047c4750d7bfb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 10:05:35 -0500 Subject: [PATCH 02/10] [skills][workflows] Fix rubber-duck findings in SDK skill and skill-runner Address three issues found in a rubber-duck review of the previous squashed commit: 1. fetch_repo_package.cs: revision sort/display key ignored , so preview builds with different preview numbers (e.g. 36.0.0-rc1 vs 36.0.0-rc2) collapsed to the same sort key as each other and as the stable release, making "sorted newest first" unreliable under --all. The key is now a 4-tuple including preview number, with stable releases (preview == 0) always sorting above previews of the same major.minor.micro -- verified live against Google's manifest that build-tools 37.0.0 now sorts above 37.0.0-rc2 above 37.0.0-rc1. 2. skill-runner.md: an explicit workflow_dispatch `skill` input was accepted as long as `.github/skills//SKILL.md` existed on disk, bypassing the documented ELIGIBLE_SKILLS allowlist entirely. The bootstrap step now validates the input is an exact member of ELIGIBLE_SKILLS before using it, and fails fast if that array is ever empty instead of dividing by zero in `RANDOM % COUNT`. 3. SKILL.md carved out a rare exception allowing package.xml.in to change alongside Configuration.props/androidsdk.targets, but the skill-runner workflow's create-pull-request.allowed-files never included it -- a validated run could hit that rare path and then be unable to open its PR. Removed the exception: this skill's scope is package pins only, and automated runs must stay within the two files the workflow is authorized to touch. Recompiled skill-runner.lock.yml with `gh aw compile --approve`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../update-androidsdk-packages/SKILL.md | 2 +- .../scripts/fetch_repo_package.cs | 17 ++++++++++---- .github/workflows/skill-runner.lock.yml | 4 ++-- .github/workflows/skill-runner.md | 23 ++++++++++++++++--- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.github/skills/update-androidsdk-packages/SKILL.md b/.github/skills/update-androidsdk-packages/SKILL.md index e28c6005c6e..a40f288a276 100644 --- a/.github/skills/update-androidsdk-packages/SKILL.md +++ b/.github/skills/update-androidsdk-packages/SKILL.md @@ -150,7 +150,7 @@ dotnet build src/androidsdk/androidsdk.csproj --no-restore -v:minimal -t:_AddPla Also check: - **XML validity** — both edited files still parse (`dotnet build` will fail loudly on malformed XML, but a quick sanity check like `powershell -Command "[xml](Get-Content src/androidsdk/androidsdk.targets)"` catches issues faster). -- **Diff cleanliness** — `git status` and `git diff` should show changes *only* in `Configuration.props` and `src/androidsdk/androidsdk.targets` (plus `package.xml.in` only if you deliberately changed the generated-package-xml template, which is rare). No stray temp files from hashing (the `sha256_of_url.cs` script cleans up after itself; double check if you downloaded anything manually instead). +- **Diff cleanliness** — `git status` and `git diff` should show changes *only* in `Configuration.props` and `src/androidsdk/androidsdk.targets`. This skill's scope is package pins, not the generated-package-xml template (`package.xml.in`) — if a routine refresh seems to require touching that file too, stop and flag it rather than including it, since automated runs of this skill (e.g. the `skill-runner` workflow) are only authorized to change the two files above. No stray temp files from hashing (the `sha256_of_url.cs` script cleans up after itself; double check if you downloaded anything manually instead). - **The two hard rules above** — diff the NDK properties and the `_PlatformPackage` item count/API-level set against `git diff` to confirm neither was touched/expanded. - **Formatting** — match the existing tab indentation and column alignment in both files (several `_PlatformPackage`/`_AndroidSdkPackage` lines are hand-aligned with extra spaces before ``/`` — preserve that style rather than reformatting the whole block). diff --git a/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs index 9894ae69ba9..f628728216e 100644 --- a/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs +++ b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs @@ -78,7 +78,7 @@ var revisionElem = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "revision"); var (revisionText, revisionKey) = ParseRevision(revisionElem); bool hasPreviewRevision = revisionElem?.Elements().FirstOrDefault(e => e.Name.LocalName == "preview") is { } previewElem - && int.TryParse(previewElem.Value, out var previewNum) && previewNum != 0; + && int.TryParse(previewElem.Value, out var previewNum0) && previewNum0 != 0; var displayName = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "display-name")?.Value ?? ""; // is a direct child of /, not of . var channelRef = pkg.Elements().FirstOrDefault(e => e.Name.LocalName == "channelRef")?.Attribute("ref")?.Value ?? ""; @@ -117,10 +117,10 @@ static void PrintUsage() Console.Error.WriteLine(" --archives print archive URLs + SHA-1 + size for each match (SHA-1 only; recompute SHA-256 from the actual download)"); } -static (string Text, (int, int, int) Key) ParseRevision(XElement? revisionElem) +static (string Text, (int, int, int, int) Key) ParseRevision(XElement? revisionElem) { if (revisionElem is null) - return ("", (0, 0, 0)); + return ("", (0, 0, 0, 0)); int Int(string name) { var s = revisionElem.Elements().FirstOrDefault(e => e.Name.LocalName == name)?.Value; @@ -132,10 +132,17 @@ int Int(string name) { int major = Int("major"); string? minor = Str("minor"); string? micro = Str("micro"); + string? preview = Str("preview"); + int previewNum = int.TryParse(preview, out var p) ? p : 0; var parts = new List { major.ToString() }; if (!string.IsNullOrEmpty(minor)) parts.Add(minor); if (!string.IsNullOrEmpty(micro)) parts.Add(micro); - return (string.Join(".", parts), (major, Int("minor"), Int("micro"))); + if (!string.IsNullOrEmpty(preview) && previewNum != 0) parts.Add($"rc{preview}"); + // Sort key includes preview as its own component so e.g. 36.0.0-preview1 and + // 36.0.0-preview2 don't collapse to the same key, and so a stable release + // (previewNum == 0) always sorts *after* any preview of the same + // major.minor.micro (Google publishes previews before promoting to stable). + return (string.Join(".", parts), (major, Int("minor"), Int("micro"), previewNum == 0 ? int.MaxValue : previewNum)); } static List CollectArchives(XElement pkg, string manifestUrl) @@ -166,5 +173,5 @@ static List CollectArchives(XElement pkg, string manifestUrl) return result; } -record PackageMatch(string Path, string Revision, (int, int, int) RevisionKey, string DisplayName, string ChannelRef, bool PreviewGuess, List Archives); +record PackageMatch(string Path, string Revision, (int, int, int, int) RevisionKey, string DisplayName, string ChannelRef, bool PreviewGuess, List Archives); record ArchiveInfo(string HostOs, string HostArch, string Url, string Sha1, string Size); diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 479048831e1..7b76286c7b4 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"49a676d9bfda6b36487f4de86d3ddc29cb660a28e459a7d07bad471e974e9a1e","body_hash":"51b603bd3c1fb3cc71e30049940ab2fb6d54de7914121be5744f4208f2b451d0","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"dd973ccdc159fd64cd0653e4f71a1588fe714ef8252526cb326037505d375c20","body_hash":"51b603bd3c1fb3cc71e30049940ab2fb6d54de7914121be5744f4208f2b451d0","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -436,7 +436,7 @@ jobs: - env: INPUT_SKILL: ${{ inputs.skill }} name: Select skill and bootstrap prerequisites - run: "mkdir -p /tmp/gh-aw/agent\nSKILL_ROOT=\".github/skills\"\n# The set of skills this workflow is allowed to run unattended. Keep this\n# in sync with the workflow_dispatch `skill` options list above.\nELIGIBLE_SKILLS=(\"update-androidsdk-packages\")\n\nif [ -n \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"$INPUT_SKILL\"\nelse\n # No explicit selection (scheduled run, or manual dispatch left blank):\n # pick uniformly at random from the eligible skills, same as\n # nightly-fix-finder does for its scan scripts. With only one skill\n # registered today this always picks it; once more are added, each\n # unselected run picks one at random.\n COUNT=${#ELIGIBLE_SKILLS[@]}\n SKILL_NAME=\"${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}\"\nfi\n\nSKILL_PATH=\"$SKILL_ROOT/$SKILL_NAME/SKILL.md\"\nif [ ! -f \"$SKILL_PATH\" ]; then\n echo \"❌ Requested skill not found: $SKILL_PATH\" >&2\n exit 1\nfi\necho \"$SKILL_NAME\" > /tmp/gh-aw/agent/selected-skill.txt\necho \"✅ Selected skill: $SKILL_NAME ($SKILL_PATH)\"\n\n# Skill-specific prerequisite bootstrap. androidsdk.csproj requires the\n# BootstrapTasks assembly to evaluate at all, so build it up front whenever\n# that skill is selected. Add an `elif` here for future skills that need\n# their own bootstrap step, rather than special-casing it in the prompt.\nif [ \"$SKILL_NAME\" = \"update-androidsdk-packages\" ]; then\n dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal\nfi" + run: "mkdir -p /tmp/gh-aw/agent\nSKILL_ROOT=\".github/skills\"\n# The set of skills this workflow is allowed to run unattended. Keep this\n# in sync with the workflow_dispatch `skill` options list above. This is\n# an allowlist: an explicit dispatch input must still be a member of this\n# array, so a stray/unregistered skill directory can never be run just by\n# naming it in workflow_dispatch, even if it happens to exist on disk.\nELIGIBLE_SKILLS=(\"update-androidsdk-packages\")\nCOUNT=${#ELIGIBLE_SKILLS[@]}\nif [ \"$COUNT\" -eq 0 ]; then\n echo \"❌ ELIGIBLE_SKILLS is empty — nothing to run.\" >&2\n exit 1\nfi\n\nif [ -n \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"\"\n for candidate in \"${ELIGIBLE_SKILLS[@]}\"; do\n if [ \"$candidate\" = \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"$candidate\"\n break\n fi\n done\n if [ -z \"$SKILL_NAME\" ]; then\n echo \"❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}\" >&2\n exit 1\n fi\nelse\n # No explicit selection (scheduled run, or manual dispatch left blank):\n # pick uniformly at random from the eligible skills, same as\n # nightly-fix-finder does for its scan scripts. With only one skill\n # registered today this always picks it; once more are added, each\n # unselected run picks one at random.\n SKILL_NAME=\"${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}\"\nfi\n\nSKILL_PATH=\"$SKILL_ROOT/$SKILL_NAME/SKILL.md\"\nif [ ! -f \"$SKILL_PATH\" ]; then\n echo \"❌ Requested skill not found: $SKILL_PATH\" >&2\n exit 1\nfi\necho \"$SKILL_NAME\" > /tmp/gh-aw/agent/selected-skill.txt\necho \"✅ Selected skill: $SKILL_NAME ($SKILL_PATH)\"\n\n# Skill-specific prerequisite bootstrap. androidsdk.csproj requires the\n# BootstrapTasks assembly to evaluate at all, so build it up front whenever\n# that skill is selected. Add an `elif` here for future skills that need\n# their own bootstrap step, rather than special-casing it in the prompt.\nif [ \"$SKILL_NAME\" = \"update-androidsdk-packages\" ]; then\n dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal\nfi" - name: Configure Git credentials env: diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 8307d2d1700..f879d54f442 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -91,18 +91,35 @@ steps: mkdir -p /tmp/gh-aw/agent SKILL_ROOT=".github/skills" # The set of skills this workflow is allowed to run unattended. Keep this - # in sync with the workflow_dispatch `skill` options list above. + # in sync with the workflow_dispatch `skill` options list above. This is + # an allowlist: an explicit dispatch input must still be a member of this + # array, so a stray/unregistered skill directory can never be run just by + # naming it in workflow_dispatch, even if it happens to exist on disk. ELIGIBLE_SKILLS=("update-androidsdk-packages") + COUNT=${#ELIGIBLE_SKILLS[@]} + if [ "$COUNT" -eq 0 ]; then + echo "❌ ELIGIBLE_SKILLS is empty — nothing to run." >&2 + exit 1 + fi if [ -n "$INPUT_SKILL" ]; then - SKILL_NAME="$INPUT_SKILL" + SKILL_NAME="" + for candidate in "${ELIGIBLE_SKILLS[@]}"; do + if [ "$candidate" = "$INPUT_SKILL" ]; then + SKILL_NAME="$candidate" + break + fi + done + if [ -z "$SKILL_NAME" ]; then + echo "❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}" >&2 + exit 1 + fi else # No explicit selection (scheduled run, or manual dispatch left blank): # pick uniformly at random from the eligible skills, same as # nightly-fix-finder does for its scan scripts. With only one skill # registered today this always picks it; once more are added, each # unselected run picks one at random. - COUNT=${#ELIGIBLE_SKILLS[@]} SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" fi From d8702b69e077167d5442db2c639151d260a61c31 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 10:19:08 -0500 Subject: [PATCH 03/10] [skills] Avoid null-forgiving operator in sha256_of_url.cs Replace `sha256.Hash!` with an explicit null-check that throws InvalidOperationException, per repo C# guidance banning the null-forgiving operator. Verified the script still hashes correctly against a real archive (build-tools_r37_linux.zip). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../skills/update-androidsdk-packages/scripts/sha256_of_url.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs index a8702d2d95b..3c3b374a493 100644 --- a/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs +++ b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs @@ -53,7 +53,7 @@ await cryptoStream.FlushFinalBlockAsync(); size = new FileInfo(tmpPath).Length; - string digest = Convert.ToHexString(sha256.Hash!); + string digest = Convert.ToHexString(sha256.Hash ?? throw new InvalidOperationException("SHA256 hash was not computed.")); Console.WriteLine($"url: {url}"); Console.WriteLine($"size: {size} bytes"); Console.WriteLine($"sha256: {digest}"); From e58e67daba8f13fa3062b9dd981592bb0565fd1f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 12:25:48 -0500 Subject: [PATCH 04/10] [skills] Validate archive metadata before hashing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .../update-androidsdk-packages/SKILL.md | 5 +- .../scripts/sha256_of_url.cs | 57 ++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.github/skills/update-androidsdk-packages/SKILL.md b/.github/skills/update-androidsdk-packages/SKILL.md index a40f288a276..d2600d3c211 100644 --- a/.github/skills/update-androidsdk-packages/SKILL.md +++ b/.github/skills/update-androidsdk-packages/SKILL.md @@ -95,12 +95,13 @@ guess a SHA-256** — either: - Compute it yourself by downloading the exact archive URL and hashing it: ```bash -dotnet run .github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip +dotnet run .github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip --sha1 --size ``` This script downloads into a scratch temp file (not `$(AndroidToolchainCacheDirectory)`, which defaults to `$HOME/android-archives` and is the repo's real download cache) and deletes the -file once hashed, so it never pollutes the cache or shows up as an untracked file in `git status`. +file once hashed. It validates the downloaded bytes against the manifest's SHA-1 and size before +printing the SHA-256, so it never trusts a successful HTTP response for the wrong archive. Do this for every host archive you're updating — Windows, Linux, macOS, and macOS arm64 when Google publishes a separate Apple Silicon archive (as it started doing for command-line tools; check whether the manifest/download page now lists a `mac_arm64` alongside `mac_x86_64` before assuming diff --git a/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs index 3c3b374a493..4b364391965 100644 --- a/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs +++ b/.github/skills/update-androidsdk-packages/scripts/sha256_of_url.cs @@ -6,7 +6,7 @@ // repository manifests only carry SHA-1). Never hand-guess a hash. // // Usage: -// dotnet run sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip +// dotnet run sha256_of_url.cs -- https://dl.google.com/android/repository/build-tools_r37.0.0_linux.zip --sha1 --size // // Deletes the downloaded file after hashing unless --keep is passed, so it // never leaves stray files behind for the diff/cache-cleanliness check. @@ -14,17 +14,34 @@ using System.Security.Cryptography; string? url = null; +string? expectedSha1 = null; +long? expectedSize = null; bool keep = false; -foreach (var a in args) { - if (a == "--keep") - keep = true; - else if (!a.StartsWith("--", StringComparison.Ordinal)) - url = a; +for (int i = 0; i < args.Length; i++) { + switch (args [i]) { + case "--keep": + keep = true; + break; + case "--sha1": + expectedSha1 = ++i < args.Length ? args [i] : null; + break; + case "--size": + if (++i >= args.Length || !long.TryParse(args [i], out var size) || size < 0) { + Console.Error.WriteLine("error: --size must be a non-negative byte count"); + return 1; + } + expectedSize = size; + break; + default: + if (!args [i].StartsWith("--", StringComparison.Ordinal)) + url = args [i]; + break; + } } -if (string.IsNullOrEmpty(url)) { - Console.Error.WriteLine("usage: dotnet run sha256_of_url.cs -- [--keep]"); +if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(expectedSha1) || expectedSize is null) { + Console.Error.WriteLine("usage: dotnet run sha256_of_url.cs -- --sha1 --size [--keep]"); return 1; } @@ -43,19 +60,31 @@ http.Timeout = TimeSpan.FromMinutes(10); long size = 0; - using (var sha256 = SHA256.Create()) + using (var sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1)) + using (var sha256 = IncrementalHash.CreateHash(HashAlgorithmName.SHA256)) using (var response = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); await using var httpStream = await response.Content.ReadAsStreamAsync(); await using var fileStream = File.Create(tmpPath); - await using var cryptoStream = new CryptoStream(fileStream, sha256, CryptoStreamMode.Write); - await httpStream.CopyToAsync(cryptoStream); - await cryptoStream.FlushFinalBlockAsync(); - size = new FileInfo(tmpPath).Length; + var buffer = new byte [81920]; + int bytesRead; + while ((bytesRead = await httpStream.ReadAsync(buffer)) != 0) { + sha1.AppendData(buffer, 0, bytesRead); + sha256.AppendData(buffer, 0, bytesRead); + await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead)); + size += bytesRead; + } + + string actualSha1 = Convert.ToHexString(sha1.GetHashAndReset()); + if (!string.Equals(actualSha1, expectedSha1, StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException($"SHA-1 mismatch: manifest={expectedSha1}, downloaded={actualSha1}"); + if (size != expectedSize.Value) + throw new InvalidDataException($"size mismatch: manifest={expectedSize.Value}, downloaded={size}"); - string digest = Convert.ToHexString(sha256.Hash ?? throw new InvalidOperationException("SHA256 hash was not computed.")); + string digest = Convert.ToHexString(sha256.GetHashAndReset()); Console.WriteLine($"url: {url}"); Console.WriteLine($"size: {size} bytes"); + Console.WriteLine($"sha1: {actualSha1}"); Console.WriteLine($"sha256: {digest}"); } From 1a3f6a186bc8bd9c993a0814bc7d515daef63c17 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 14:58:30 -0500 Subject: [PATCH 05/10] [skills][workflows] Address review blockers Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .../update-androidsdk-packages/SKILL.md | 5 +++-- .../scripts/fetch_repo_package.cs | 20 ++++++++++++++++--- .github/workflows/skill-runner.lock.yml | 9 ++++++--- .github/workflows/skill-runner.md | 13 ++++++++++-- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.github/skills/update-androidsdk-packages/SKILL.md b/.github/skills/update-androidsdk-packages/SKILL.md index d2600d3c211..02d20aadb34 100644 --- a/.github/skills/update-androidsdk-packages/SKILL.md +++ b/.github/skills/update-androidsdk-packages/SKILL.md @@ -127,8 +127,9 @@ one shared macOS zip still covers both). (e.g. `platform-34-ext7_r02` → `platform-34-ext12_r01`) — do not introduce an extension suffix for an API level that never had one, or vice versa, without a clear reason from the manifest. - The `source-NN_r0M.zip` sources package and `XAAndroidSourcesHash` should track whichever API level - is `IsLatestStable="true"` in `_PlatformPackage` (see the existing `` path — it embeds - the API level, e.g. `\sources\android-37.0`). Update both the zip name/Destination and the hash + is `IsLatestStable="true"` in `_PlatformPackage` (the `` uses the integer API level, + e.g. `\sources\android-37`, even when the catalog entry is `platform-37.0_r01`). Update both the + zip name/Destination and the hash together if the latest stable API level's source archive changed. ### 5. Validate before finishing diff --git a/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs index f628728216e..04016e21599 100644 --- a/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs +++ b/.github/skills/update-androidsdk-packages/scripts/fetch_repo_package.cs @@ -87,7 +87,7 @@ continue; var archives = showArchives ? CollectArchives(pkg, manifest) : new List(); - matches.Add(new PackageMatch(pkgPath, revisionText, revisionKey, displayName, channelRef, preview, archives)); + matches.Add(new PackageMatch(pkgPath, revisionText, revisionKey, displayName, channelRef, preview, ParsePlatformKey(pkgPath), archives)); } if (matches.Count == 0) { @@ -95,7 +95,11 @@ return 1; } -foreach (var m in matches.OrderByDescending(m => m.RevisionKey)) { +foreach (var m in matches + .OrderByDescending(m => m.PlatformKey?.Major ?? -1) + .ThenByDescending(m => m.PlatformKey?.Minor ?? -1) + .ThenByDescending(m => m.PlatformKey?.Extension ?? -1) + .ThenByDescending(m => m.RevisionKey)) { string flag = m.PreviewGuess ? " [PREVIEW-LOOKING]" : ""; Console.WriteLine($"{m.Path} rev={m.Revision} channel={m.ChannelRef}{flag} {m.DisplayName}"); foreach (var a in m.Archives) { @@ -173,5 +177,15 @@ static List CollectArchives(XElement pkg, string manifestUrl) return result; } -record PackageMatch(string Path, string Revision, (int, int, int, int) RevisionKey, string DisplayName, string ChannelRef, bool PreviewGuess, List Archives); +static (int Major, int Minor, int Extension)? ParsePlatformKey(string path) +{ + var match = Regex.Match(path, @"(?:^|;)android-(\d+)(?:\.(\d+))?(?:-ext(\d+))?(?:$|;)", RegexOptions.IgnoreCase); + if (!match.Success || !int.TryParse(match.Groups [1].Value, out var major)) + return null; + int.TryParse(match.Groups [2].Value, out var minor); + int.TryParse(match.Groups [3].Value, out var extension); + return (major, minor, extension); +} + +record PackageMatch(string Path, string Revision, (int, int, int, int) RevisionKey, string DisplayName, string ChannelRef, bool PreviewGuess, (int Major, int Minor, int Extension)? PlatformKey, List Archives); record ArchiveInfo(string HostOs, string HostArch, string Url, string Sha1, string Size); diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 7b76286c7b4..1e0956f19aa 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"dd973ccdc159fd64cd0653e4f71a1588fe714ef8252526cb326037505d375c20","body_hash":"51b603bd3c1fb3cc71e30049940ab2fb6d54de7914121be5744f4208f2b451d0","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"90743c4f66cbcc0e64687dee09e04b05facbc7ba26101a539d4cbefd611c5ef6","body_hash":"877f29bbdb16fa44a5a9425208c84b925c54c4b5b9fb854d9578cc6f89292591","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -89,8 +89,9 @@ on: permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" - cancel-in-progress: true + cancel-in-progress: false + group: skill-runner + queue: max run-name: "Skill Runner" @@ -417,6 +418,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + ref: main fetch-depth: 0 - name: Setup .NET uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 @@ -1759,6 +1761,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: true + ref: main fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Configure Git credentials diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index f879d54f442..7b801c223ac 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -22,6 +22,10 @@ permissions: contents: read issues: read pull-requests: read +concurrency: + group: skill-runner + cancel-in-progress: false + queue: max # ############################################################### # Select a PAT from the pool and override COPILOT_GITHUB_TOKEN. # Run agentic jobs in an isolated `copilot-pat-pool` environment. @@ -41,6 +45,7 @@ imports: environment: copilot-pat-pool checkout: - fetch-depth: 0 + ref: main jobs: conclusion: permissions: @@ -241,13 +246,17 @@ go straight to Phase 4 and report the failure instead. ## Phase 3: Commit and Open the PR (only if changes were made and validated) -1. Commit with a concise message describing exactly what changed, per the skill's own guidance, ending +1. Before making changes or opening a PR, inspect open pull requests targeting `main` for an existing + update from this workflow and selected skill (for example, using `gh pr list --state open --base main` + and checking the title/body). If an equivalent open update PR already exists, do not create another + one; report a no-op with the existing PR number and continue to Phase 4. +2. Commit with a concise message describing exactly what changed, per the skill's own guidance, ending in: `Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>` The workflow configures Git as `github-actions[bot]` — keep that author identity. -2. Call `create_pull_request` exactly once. Use a short branch name and a PR body with this +3. Call `create_pull_request` exactly once. Use a short branch name, explicitly target `main`, and use a PR body with this structure: ```markdown From d2aefe472e8907f71bf35790c1436c7c43931644 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 15:43:29 -0500 Subject: [PATCH 06/10] Fix skill-runner workflow blockers Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .github/workflows/skill-runner.lock.yml | 61 ++++++++++++++++++++++++- .github/workflows/skill-runner.md | 22 +++++++-- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 1e0956f19aa..6ad1e4d44dd 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"90743c4f66cbcc0e64687dee09e04b05facbc7ba26101a539d4cbefd611c5ef6","body_hash":"877f29bbdb16fa44a5a9425208c84b925c54c4b5b9fb854d9578cc6f89292591","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"74dc2ebfd9271b34b3759f5dda743c2d910c39e6d5da6b0638b6e0f0c9ca5cbb","body_hash":"25a22231bf2349a1a6114e5dadbae5169e40225ff97f707b98a4e698c5fd982f","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -435,10 +435,67 @@ jobs: with: name: activation path: /tmp/gh-aw + - env: + GH_AW_GITHUB_REF: ${{ github.ref }} + if: ${{ github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' }} + name: Reject non-main workflow dispatches + run: | + echo "This workflow only runs from the main branch; refusing workflow_dispatch from $GH_AW_GITHUB_REF" >&2 + exit 1 - env: INPUT_SKILL: ${{ inputs.skill }} name: Select skill and bootstrap prerequisites - run: "mkdir -p /tmp/gh-aw/agent\nSKILL_ROOT=\".github/skills\"\n# The set of skills this workflow is allowed to run unattended. Keep this\n# in sync with the workflow_dispatch `skill` options list above. This is\n# an allowlist: an explicit dispatch input must still be a member of this\n# array, so a stray/unregistered skill directory can never be run just by\n# naming it in workflow_dispatch, even if it happens to exist on disk.\nELIGIBLE_SKILLS=(\"update-androidsdk-packages\")\nCOUNT=${#ELIGIBLE_SKILLS[@]}\nif [ \"$COUNT\" -eq 0 ]; then\n echo \"❌ ELIGIBLE_SKILLS is empty — nothing to run.\" >&2\n exit 1\nfi\n\nif [ -n \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"\"\n for candidate in \"${ELIGIBLE_SKILLS[@]}\"; do\n if [ \"$candidate\" = \"$INPUT_SKILL\" ]; then\n SKILL_NAME=\"$candidate\"\n break\n fi\n done\n if [ -z \"$SKILL_NAME\" ]; then\n echo \"❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}\" >&2\n exit 1\n fi\nelse\n # No explicit selection (scheduled run, or manual dispatch left blank):\n # pick uniformly at random from the eligible skills, same as\n # nightly-fix-finder does for its scan scripts. With only one skill\n # registered today this always picks it; once more are added, each\n # unselected run picks one at random.\n SKILL_NAME=\"${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}\"\nfi\n\nSKILL_PATH=\"$SKILL_ROOT/$SKILL_NAME/SKILL.md\"\nif [ ! -f \"$SKILL_PATH\" ]; then\n echo \"❌ Requested skill not found: $SKILL_PATH\" >&2\n exit 1\nfi\necho \"$SKILL_NAME\" > /tmp/gh-aw/agent/selected-skill.txt\necho \"✅ Selected skill: $SKILL_NAME ($SKILL_PATH)\"\n\n# Skill-specific prerequisite bootstrap. androidsdk.csproj requires the\n# BootstrapTasks assembly to evaluate at all, so build it up front whenever\n# that skill is selected. Add an `elif` here for future skills that need\n# their own bootstrap step, rather than special-casing it in the prompt.\nif [ \"$SKILL_NAME\" = \"update-androidsdk-packages\" ]; then\n dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal\nfi" + run: |- + mkdir -p /tmp/gh-aw/agent + SKILL_ROOT=".github/skills" + # The set of skills this workflow is allowed to run unattended. Keep this + # in sync with the workflow_dispatch `skill` options list above. This is + # an allowlist: an explicit dispatch input must still be a member of this + # array, so a stray/unregistered skill directory can never be run just by + # naming it in workflow_dispatch, even if it happens to exist on disk. + ELIGIBLE_SKILLS=("update-androidsdk-packages") + COUNT=${#ELIGIBLE_SKILLS[@]} + if [ "$COUNT" -eq 0 ]; then + echo "❌ ELIGIBLE_SKILLS is empty — nothing to run." >&2 + exit 1 + fi + + if [ -n "$INPUT_SKILL" ]; then + SKILL_NAME="" + for candidate in "${ELIGIBLE_SKILLS[@]}"; do + if [ "$candidate" = "$INPUT_SKILL" ]; then + SKILL_NAME="$candidate" + break + fi + done + if [ -z "$SKILL_NAME" ]; then + echo "❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}" >&2 + exit 1 + fi + else + # No explicit selection (scheduled run, or manual dispatch left blank): + # pick uniformly at random from the eligible skills, same as + # nightly-fix-finder does for its scan scripts. With only one skill + # registered today this always picks it; once more are added, each + # unselected run picks one at random. + SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" + fi + + SKILL_PATH="$SKILL_ROOT/$SKILL_NAME/SKILL.md" + if [ ! -f "$SKILL_PATH" ]; then + echo "❌ Requested skill not found: $SKILL_PATH" >&2 + exit 1 + fi + echo "$SKILL_NAME" > /tmp/gh-aw/agent/selected-skill.txt + echo "✅ Selected skill: $SKILL_NAME ($SKILL_PATH)" + + # Skill-specific prerequisite bootstrap. androidsdk.csproj requires the + # BootstrapTasks assembly to evaluate at all, so build it up front whenever + # that skill is selected. Add an `elif` here for future skills that need + # their own bootstrap step, rather than special-casing it in the prompt. + if [ "$SKILL_NAME" = "update-androidsdk-packages" ]; then + dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal + fi - name: Configure Git credentials env: diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 7b801c223ac..3254758bd88 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -43,6 +43,9 @@ imports: environment: copilot-pat-pool environment: copilot-pat-pool +# This workflow intentionally operates only from main. Manual dispatches from any +# other ref are rejected before activation, and PR-context checkout is disabled +# so generated workflow context cannot swap the workspace onto a branch checkout. checkout: - fetch-depth: 0 ref: main @@ -89,6 +92,11 @@ safe-outputs: create-issue: false report-failure-as-issue: true steps: +- name: Reject non-main workflow dispatches + if: ${{ github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' }} + run: | + echo "This workflow only runs from the main branch; refusing workflow_dispatch from ${{ github.ref }}" >&2 + exit 1 - env: INPUT_SKILL: ${{ inputs.skill }} name: Select skill and bootstrap prerequisites @@ -246,10 +254,16 @@ go straight to Phase 4 and report the failure instead. ## Phase 3: Commit and Open the PR (only if changes were made and validated) -1. Before making changes or opening a PR, inspect open pull requests targeting `main` for an existing - update from this workflow and selected skill (for example, using `gh pr list --state open --base main` - and checking the title/body). If an equivalent open update PR already exists, do not create another - one; report a no-op with the existing PR number and continue to Phase 4. +1. Immediately before creating a PR, run a deterministic open-PR check keyed to this workflow and the + selected skill. Use a paginated search/filter that matches the exact title prefix for this workflow, + for example: + + `gh pr list --state open --base main --limit 100 --search '"[skill-runner]" ""' --json number,title,headRefName,body` + + and compare the returned titles against the exact prefix `"[skill-runner] "`. If any open + PR matches that same workflow/skill key, do not create another PR; report a no-op with the existing + PR number and continue to Phase 4. Do not fall back to a looser heuristic or a manually judged + "equivalent" match. 2. Commit with a concise message describing exactly what changed, per the skill's own guidance, ending in: From d1d133c5d7e19fa82733af5a38edb54f43447bb4 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 13 Aug 2026 21:56:26 -0500 Subject: [PATCH 07/10] Tighten skill-runner guardrails Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .github/workflows/skill-runner.lock.yml | 11 ++++++++--- .github/workflows/skill-runner.md | 18 ++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 6ad1e4d44dd..04b8165f107 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"74dc2ebfd9271b34b3759f5dda743c2d910c39e6d5da6b0638b6e0f0c9ca5cbb","body_hash":"25a22231bf2349a1a6114e5dadbae5169e40225ff97f707b98a4e698c5fd982f","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"be720f1bc2703a0f4511e1e872a179c783fbf5af2c3b679aa7f54d43e34ef54f","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -101,7 +101,7 @@ jobs: - pat_pool - pre_activation if: > - needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main') && needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || github.event.pull_request.stack.position == github.event.pull_request.stack.size)) runs-on: ubuntu-slim @@ -442,6 +442,11 @@ jobs: run: | echo "This workflow only runs from the main branch; refusing workflow_dispatch from $GH_AW_GITHUB_REF" >&2 exit 1 + - if: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' }} + name: Reject PR ambient context on manual dispatch + run: | + echo "Manual dispatch with PR aw_context is forbidden; refusing to checkout or act on branch-controlled PR context." >&2 + exit 1 - env: INPUT_SKILL: ${{ inputs.skill }} name: Select skill and bootstrap prerequisites @@ -506,7 +511,7 @@ jobs: - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + github.event.pull_request || github.event.issue.pull_request uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 3254758bd88..957b398c10c 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -97,6 +97,11 @@ steps: run: | echo "This workflow only runs from the main branch; refusing workflow_dispatch from ${{ github.ref }}" >&2 exit 1 +- name: Reject PR ambient context on manual dispatch + if: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' }} + run: | + echo "Manual dispatch with PR aw_context is forbidden; refusing to checkout or act on branch-controlled PR context." >&2 + exit 1 - env: INPUT_SKILL: ${{ inputs.skill }} name: Select skill and bootstrap prerequisites @@ -255,15 +260,16 @@ go straight to Phase 4 and report the failure instead. ## Phase 3: Commit and Open the PR (only if changes were made and validated) 1. Immediately before creating a PR, run a deterministic open-PR check keyed to this workflow and the - selected skill. Use a paginated search/filter that matches the exact title prefix for this workflow, - for example: + selected skill. Search all open PRs against `main` using the exact workflow title prefix, with a + paginated query that is not limited to the first page and not derived from a loose semantic match: `gh pr list --state open --base main --limit 100 --search '"[skill-runner]" ""' --json number,title,headRefName,body` - and compare the returned titles against the exact prefix `"[skill-runner] "`. If any open - PR matches that same workflow/skill key, do not create another PR; report a no-op with the existing - PR number and continue to Phase 4. Do not fall back to a looser heuristic or a manually judged - "equivalent" match. + Repeat/paginate until exhausted, then compare every returned title against the exact prefix + `"[skill-runner] "`. If any open PR matches that same workflow/skill key, do not create + another PR; report a no-op with the existing PR number and continue to Phase 4. Do not fall back to + a looser heuristic or a manually judged "equivalent" match. The uniqueness check is mandatory and + must be enforced before any `create_pull_request` call. 2. Commit with a concise message describing exactly what changed, per the skill's own guidance, ending in: From 354bd426bf9a370285732420bd0d32a9a678ade9 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 14 Aug 2026 08:41:59 -0500 Subject: [PATCH 08/10] Enforce skill-runner workflow guards Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .github/workflows/skill-runner.lock.yml | 50 ++++++++++++++++++++++--- .github/workflows/skill-runner.md | 16 ++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 04b8165f107..4b71f39e593 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"be720f1bc2703a0f4511e1e872a179c783fbf5af2c3b679aa7f54d43e34ef54f","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f4d0ea8f73a3a9eae84d8b9eafbc9b1d87e33489b59570c7843207e967911baa","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -71,6 +71,20 @@ on: - .github/workflows/skill-runner.__never_matches__ schedule: - cron: "18 3 * * 1" # Friendly format: weekly on monday around 03:00 (scattered) + # skip-if-match: is:pr is:open in:title "[skill-runner] update-androidsdk-packages" # Skip-if-match processed as search check in pre-activation job + # steps: # Steps injected into pre-activation job + # - env: + # WORKFLOW_REF: ${{ github.ref }} + # if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' + # name: Reject non-main workflow dispatches before activation + # run: | + # echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 + # exit 1 + # - if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + # name: Reject PR ambient context on manual dispatch + # run: | + # echo "Manual dispatch with PR aw_context is forbidden." >&2 + # exit 1 workflow_dispatch: inputs: aw_context: @@ -101,9 +115,10 @@ jobs: - pat_pool - pre_activation if: > - (github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main') && needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && + (needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || - github.event.pull_request.stack.position == github.event.pull_request.stack.size)) + github.event.pull_request.stack.position == github.event.pull_request.stack.size))) && (github.event_name != 'workflow_dispatch' || + github.ref == 'refs/heads/main') runs-on: ubuntu-slim permissions: actions: read @@ -511,7 +526,7 @@ jobs: - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1704,7 +1719,7 @@ jobs: env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: - activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' }} matched_command: '' setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} @@ -1734,6 +1749,31 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); await main(); + - name: Check skip-if-match query + id: check_skip_if_match + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SKIP_QUERY: "is:pr is:open in:title \"[skill-runner] update-androidsdk-packages\"" + GH_AW_WORKFLOW_NAME: "Skill Runner" + GH_AW_SKIP_MAX_MATCHES: "1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_if_match.cjs'); + await main(); + - name: Reject non-main workflow dispatches before activation + if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' + run: | + echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 + exit 1 + env: + WORKFLOW_REF: ${{ github.ref }} + - name: Reject PR ambient context on manual dispatch + if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + run: | + echo "Manual dispatch with PR aw_context is forbidden." >&2 + exit 1 safe_outputs: needs: diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 957b398c10c..6b537d39052 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -18,6 +18,20 @@ on: - "update-androidsdk-packages" required: false type: choice + steps: + - name: Reject non-main workflow dispatches before activation + if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' + env: + WORKFLOW_REF: ${{ github.ref }} + run: | + echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 + exit 1 + - name: Reject PR ambient context on manual dispatch + if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + run: | + echo "Manual dispatch with PR aw_context is forbidden." >&2 + exit 1 + skip-if-match: 'is:pr is:open in:title "[skill-runner] update-androidsdk-packages"' permissions: contents: read issues: read @@ -50,6 +64,8 @@ checkout: - fetch-depth: 0 ref: main jobs: + activation: + if: github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main' conclusion: permissions: issues: write From 43b5204b44537141fa60815bbe3eaa0bf8c3ada7 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 14 Aug 2026 09:17:38 -0500 Subject: [PATCH 09/10] Address skill-runner review blockers Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .../update-androidsdk-packages/SKILL.md | 1 + .github/workflows/skill-runner.lock.yml | 124 ++++++++------- .github/workflows/skill-runner.md | 149 +++++++++--------- 3 files changed, 143 insertions(+), 131 deletions(-) diff --git a/.github/skills/update-androidsdk-packages/SKILL.md b/.github/skills/update-androidsdk-packages/SKILL.md index 02d20aadb34..eb782b62fb0 100644 --- a/.github/skills/update-androidsdk-packages/SKILL.md +++ b/.github/skills/update-androidsdk-packages/SKILL.md @@ -139,6 +139,7 @@ Run these in order — do not skip the BootstrapTasks build; `androidsdk.csproj` ```bash dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal +dotnet restore src/androidsdk/androidsdk.csproj dotnet build src/androidsdk/androidsdk.csproj --no-restore -v:minimal ``` diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 4b71f39e593..03448d85dae 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f4d0ea8f73a3a9eae84d8b9eafbc9b1d87e33489b59570c7843207e967911baa","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1e796b0579ba01df2364bd8b8fa03403974d1881bea856ede3fc21bcd14e4c5a","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -71,20 +71,6 @@ on: - .github/workflows/skill-runner.__never_matches__ schedule: - cron: "18 3 * * 1" # Friendly format: weekly on monday around 03:00 (scattered) - # skip-if-match: is:pr is:open in:title "[skill-runner] update-androidsdk-packages" # Skip-if-match processed as search check in pre-activation job - # steps: # Steps injected into pre-activation job - # - env: - # WORKFLOW_REF: ${{ github.ref }} - # if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' - # name: Reject non-main workflow dispatches before activation - # run: | - # echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 - # exit 1 - # - if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' - # name: Reject PR ambient context on manual dispatch - # run: | - # echo "Manual dispatch with PR aw_context is forbidden." >&2 - # exit 1 workflow_dispatch: inputs: aw_context: @@ -114,6 +100,7 @@ jobs: needs: - pat_pool - pre_activation + - workflow_guard if: > (needs.pre_activation.outputs.activated == 'true' && ((github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || @@ -369,6 +356,7 @@ jobs: needs: - activation - pat_pool + - workflow_guard runs-on: ubuntu-latest environment: copilot-pat-pool permissions: @@ -445,26 +433,42 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} + - name: Start DIFC Proxy + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_HOST: ${{ env.GH_HOST }} + GITHUB_HOST: ${{ env.GITHUB_HOST }} + GITHUB_ENTERPRISE_HOST: ${{ env.GITHUB_ENTERPRISE_HOST }} + GITHUB_GRAPHQL_URL: ${{ env.GITHUB_GRAPHQL_URL }} + GITHUB_COPILOT_BASE_URL: ${{ env.GITHUB_COPILOT_BASE_URL }} + GH_AW_NETWORK_ISOLATION: 'true' + DIFC_PROXY_POLICY: '{"allow-only":{"min-integrity":"none","repos":"all"}}' + DIFC_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.4.9' + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/start_difc_proxy.sh" - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: activation path: /tmp/gh-aw - - env: - GH_AW_GITHUB_REF: ${{ github.ref }} - if: ${{ github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' }} - name: Reject non-main workflow dispatches + - name: Enforce unique skill update PR run: | - echo "This workflow only runs from the main branch; refusing workflow_dispatch from $GH_AW_GITHUB_REF" >&2 - exit 1 - - if: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' }} - name: Reject PR ambient context on manual dispatch - run: | - echo "Manual dispatch with PR aw_context is forbidden; refusing to checkout or act on branch-controlled PR context." >&2 - exit 1 - - env: - INPUT_SKILL: ${{ inputs.skill }} - name: Select skill and bootstrap prerequisites + existing_prs="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls?state=open&base=main&per_page=100" \ + --jq '.[] | select(.title == "[skill-runner] update-androidsdk-packages") | .number')" + if [ -n "$existing_prs" ]; then + printf '{"type":"noop","message":"An open [skill-runner] update-androidsdk-packages PR already exists against main: #%s. No duplicate PR will be created."}\n' \ + "$(printf '%s' "$existing_prs" | paste -sd, -)" >> "$GH_AW_SAFE_OUTPUTS" + fi + env: + GH_HOST: ${{ env.GH_HOST || 'github.com' }} + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_API_URL: https://localhost:18443/api/v3 + GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql + NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt + - name: Select skill and bootstrap prerequisites run: |- mkdir -p /tmp/gh-aw/agent SKILL_ROOT=".github/skills" @@ -516,7 +520,13 @@ jobs: if [ "$SKILL_NAME" = "update-androidsdk-packages" ]; then dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal fi - + env: + GH_HOST: ${{ env.GH_HOST || 'github.com' }} + GH_REPO: ${{ github.repository }} + GITHUB_API_URL: https://localhost:18443/api/v3 + GITHUB_GRAPHQL_URL: https://localhost:18443/api/graphql + INPUT_SKILL: ${{ inputs.skill }} + NODE_EXTRA_CA_CERTS: /tmp/gh-aw/proxy-logs/proxy-tls/ca.crt - name: Configure Git credentials env: GITHUB_REPOSITORY: ${{ github.repository }} @@ -564,6 +574,10 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Stop DIFC Proxy + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/stop_difc_proxy.sh" - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: @@ -1139,6 +1153,7 @@ jobs: - detection - pat_pool - safe_outputs + - workflow_guard if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true') @@ -1710,6 +1725,7 @@ jobs: shell: bash pre_activation: + needs: workflow_guard if: > (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id) && ((github.event_name != 'pull_request' && github.event_name != 'pull_request_review') || github.event.pull_request.stack == null || @@ -1719,7 +1735,7 @@ jobs: env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: - activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' }} + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} @@ -1749,31 +1765,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); await main(); - - name: Check skip-if-match query - id: check_skip_if_match - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_SKIP_QUERY: "is:pr is:open in:title \"[skill-runner] update-androidsdk-packages\"" - GH_AW_WORKFLOW_NAME: "Skill Runner" - GH_AW_SKIP_MAX_MATCHES: "1" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_if_match.cjs'); - await main(); - - name: Reject non-main workflow dispatches before activation - if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' - run: | - echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 - exit 1 - env: - WORKFLOW_REF: ${{ github.ref }} - - name: Reject PR ambient context on manual dispatch - if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' - run: | - echo "Manual dispatch with PR aw_context is forbidden." >&2 - exit 1 safe_outputs: needs: @@ -1910,3 +1901,24 @@ jobs: /tmp/gh-aw/safe-output-items.jsonl /tmp/gh-aw/temporary-id-map.json if-no-files-found: ignore + + workflow_guard: + runs-on: ubuntu-slim + permissions: {} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Validate workflow dispatch context + if: github.event_name == 'workflow_dispatch' && (github.ref != 'refs/heads/main' || fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request') + run: | + echo "This workflow only accepts main-branch dispatches without PR context (ref: $WORKFLOW_REF)." >&2 + exit 1 + env: + WORKFLOW_REF: ${{ github.ref }} diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 6b537d39052..4600a6c0617 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -18,20 +18,8 @@ on: - "update-androidsdk-packages" required: false type: choice - steps: - - name: Reject non-main workflow dispatches before activation - if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' - env: - WORKFLOW_REF: ${{ github.ref }} - run: | - echo "This workflow only runs from the main branch; refusing workflow_dispatch from $WORKFLOW_REF" >&2 - exit 1 - - name: Reject PR ambient context on manual dispatch - if: github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' - run: | - echo "Manual dispatch with PR aw_context is forbidden." >&2 - exit 1 - skip-if-match: 'is:pr is:open in:title "[skill-runner] update-androidsdk-packages"' + needs: + - workflow_guard permissions: contents: read issues: read @@ -64,6 +52,17 @@ checkout: - fetch-depth: 0 ref: main jobs: + workflow_guard: + runs-on: ubuntu-slim + permissions: {} + steps: + - name: Validate workflow dispatch context + if: github.event_name == 'workflow_dispatch' && (github.ref != 'refs/heads/main' || fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request') + env: + WORKFLOW_REF: ${{ github.ref }} + run: | + echo "This workflow only accepts main-branch dispatches without PR context (ref: $WORKFLOW_REF)." >&2 + exit 1 activation: if: github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main' conclusion: @@ -108,70 +107,70 @@ safe-outputs: create-issue: false report-failure-as-issue: true steps: -- name: Reject non-main workflow dispatches - if: ${{ github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' }} - run: | - echo "This workflow only runs from the main branch; refusing workflow_dispatch from ${{ github.ref }}" >&2 - exit 1 -- name: Reject PR ambient context on manual dispatch - if: ${{ github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' }} - run: | - echo "Manual dispatch with PR aw_context is forbidden; refusing to checkout or act on branch-controlled PR context." >&2 - exit 1 -- env: - INPUT_SKILL: ${{ inputs.skill }} - name: Select skill and bootstrap prerequisites - run: | - mkdir -p /tmp/gh-aw/agent - SKILL_ROOT=".github/skills" - # The set of skills this workflow is allowed to run unattended. Keep this - # in sync with the workflow_dispatch `skill` options list above. This is - # an allowlist: an explicit dispatch input must still be a member of this - # array, so a stray/unregistered skill directory can never be run just by - # naming it in workflow_dispatch, even if it happens to exist on disk. - ELIGIBLE_SKILLS=("update-androidsdk-packages") - COUNT=${#ELIGIBLE_SKILLS[@]} - if [ "$COUNT" -eq 0 ]; then - echo "❌ ELIGIBLE_SKILLS is empty — nothing to run." >&2 - exit 1 - fi - - if [ -n "$INPUT_SKILL" ]; then - SKILL_NAME="" - for candidate in "${ELIGIBLE_SKILLS[@]}"; do - if [ "$candidate" = "$INPUT_SKILL" ]; then - SKILL_NAME="$candidate" - break + - name: Enforce unique skill update PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + existing_prs="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls?state=open&base=main&per_page=100" \ + --jq '.[] | select(.title == "[skill-runner] update-androidsdk-packages") | .number')" + if [ -n "$existing_prs" ]; then + printf '{"type":"noop","message":"An open [skill-runner] update-androidsdk-packages PR already exists against main: #%s. No duplicate PR will be created."}\n' \ + "$(printf '%s' "$existing_prs" | paste -sd, -)" >> "$GH_AW_SAFE_OUTPUTS" + fi + - name: Select skill and bootstrap prerequisites + env: + INPUT_SKILL: ${{ inputs.skill }} + run: | + mkdir -p /tmp/gh-aw/agent + SKILL_ROOT=".github/skills" + # The set of skills this workflow is allowed to run unattended. Keep this + # in sync with the workflow_dispatch `skill` options list above. This is + # an allowlist: an explicit dispatch input must still be a member of this + # array, so a stray/unregistered skill directory can never be run just by + # naming it in workflow_dispatch, even if it happens to exist on disk. + ELIGIBLE_SKILLS=("update-androidsdk-packages") + COUNT=${#ELIGIBLE_SKILLS[@]} + if [ "$COUNT" -eq 0 ]; then + echo "❌ ELIGIBLE_SKILLS is empty — nothing to run." >&2 + exit 1 + fi + + if [ -n "$INPUT_SKILL" ]; then + SKILL_NAME="" + for candidate in "${ELIGIBLE_SKILLS[@]}"; do + if [ "$candidate" = "$INPUT_SKILL" ]; then + SKILL_NAME="$candidate" + break + fi + done + if [ -z "$SKILL_NAME" ]; then + echo "❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}" >&2 + exit 1 fi - done - if [ -z "$SKILL_NAME" ]; then - echo "❌ Requested skill '$INPUT_SKILL' is not in ELIGIBLE_SKILLS: ${ELIGIBLE_SKILLS[*]}" >&2 + else + # No explicit selection (scheduled run, or manual dispatch left blank): + # pick uniformly at random from the eligible skills, same as + # nightly-fix-finder does for its scan scripts. With only one skill + # registered today this always picks it; once more are added, each + # unselected run picks one at random. + SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" + fi + + SKILL_PATH="$SKILL_ROOT/$SKILL_NAME/SKILL.md" + if [ ! -f "$SKILL_PATH" ]; then + echo "❌ Requested skill not found: $SKILL_PATH" >&2 exit 1 fi - else - # No explicit selection (scheduled run, or manual dispatch left blank): - # pick uniformly at random from the eligible skills, same as - # nightly-fix-finder does for its scan scripts. With only one skill - # registered today this always picks it; once more are added, each - # unselected run picks one at random. - SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" - fi - - SKILL_PATH="$SKILL_ROOT/$SKILL_NAME/SKILL.md" - if [ ! -f "$SKILL_PATH" ]; then - echo "❌ Requested skill not found: $SKILL_PATH" >&2 - exit 1 - fi - echo "$SKILL_NAME" > /tmp/gh-aw/agent/selected-skill.txt - echo "✅ Selected skill: $SKILL_NAME ($SKILL_PATH)" - - # Skill-specific prerequisite bootstrap. androidsdk.csproj requires the - # BootstrapTasks assembly to evaluate at all, so build it up front whenever - # that skill is selected. Add an `elif` here for future skills that need - # their own bootstrap step, rather than special-casing it in the prompt. - if [ "$SKILL_NAME" = "update-androidsdk-packages" ]; then - dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal - fi + echo "$SKILL_NAME" > /tmp/gh-aw/agent/selected-skill.txt + echo "✅ Selected skill: $SKILL_NAME ($SKILL_PATH)" + + # Skill-specific prerequisite bootstrap. androidsdk.csproj requires the + # BootstrapTasks assembly to evaluate at all, so build it up front whenever + # that skill is selected. Add an `elif` here for future skills that need + # their own bootstrap step, rather than special-casing it in the prompt. + if [ "$SKILL_NAME" = "update-androidsdk-packages" ]; then + dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj -v:minimal + fi description: Weekly (or on-demand) runner that executes a selectable repository Copilot skill end to end, opens a PR for validated changes, and always reports outcome/errors on a tracking issue model: gpt-5.6-sol engine: From 1e17348b381936b072ff7f9c7102beb3957be5cd Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 14 Aug 2026 09:17:58 -0500 Subject: [PATCH 10/10] Clean skill-runner workflow formatting Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 49de4c35-38d9-4c5a-a41b-b6b017260349 --- .github/workflows/skill-runner.lock.yml | 2 +- .github/workflows/skill-runner.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/skill-runner.lock.yml b/.github/workflows/skill-runner.lock.yml index 03448d85dae..f80bea40330 100644 --- a/.github/workflows/skill-runner.lock.yml +++ b/.github/workflows/skill-runner.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1e796b0579ba01df2364bd8b8fa03403974d1881bea856ede3fc21bcd14e4c5a","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e9c6519e2d8a56039f54c652a82a194740c225ce5451d31f160e14399b5f4445","body_hash":"edea8cf6a1918dd970de8697ffd104805081056d2e6dc79c9aec177b034184a7","compiler_version":"v0.86.2","strict":true,"agent_id":"copilot","agent_model":"gpt-5.6-sol","engine_versions":{"copilot":"1.0.79"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"a98b56852c35b8e3190ac28c8c2271da59106c68","version":"v6.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"6aab9e5b5c91c615506061f09bedd81a23babe3c","version":"v0.86.2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44","digest":"sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.44@sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request":true} # This file was automatically generated by gh-aw (v0.86.2). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # diff --git a/.github/workflows/skill-runner.md b/.github/workflows/skill-runner.md index 4600a6c0617..e29af6f5784 100644 --- a/.github/workflows/skill-runner.md +++ b/.github/workflows/skill-runner.md @@ -134,7 +134,7 @@ steps: echo "❌ ELIGIBLE_SKILLS is empty — nothing to run." >&2 exit 1 fi - + if [ -n "$INPUT_SKILL" ]; then SKILL_NAME="" for candidate in "${ELIGIBLE_SKILLS[@]}"; do @@ -155,7 +155,7 @@ steps: # unselected run picks one at random. SKILL_NAME="${ELIGIBLE_SKILLS[$((RANDOM % COUNT))]}" fi - + SKILL_PATH="$SKILL_ROOT/$SKILL_NAME/SKILL.md" if [ ! -f "$SKILL_PATH" ]; then echo "❌ Requested skill not found: $SKILL_PATH" >&2 @@ -163,7 +163,7 @@ steps: fi echo "$SKILL_NAME" > /tmp/gh-aw/agent/selected-skill.txt echo "✅ Selected skill: $SKILL_NAME ($SKILL_PATH)" - + # Skill-specific prerequisite bootstrap. androidsdk.csproj requires the # BootstrapTasks assembly to evaluate at all, so build it up front whenever # that skill is selected. Add an `elif` here for future skills that need