From 61e8d5c7eb097664958e00c30ccd88bb51359043 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:09:36 -0600 Subject: [PATCH] fix(ci): keep trusted full-ci label runs on AWS (#18855) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Public pull requests need CI without access to private runner capacity. > - Trusted CI CODEOWNERS use the AWS runner fleet after identity and revision checks. > - The PR caller accepts both `profile-ci` and `full-ci` label events. > - The runner gate accepted only `profile-ci`, so `full-ci` sent trusted work to GitHub. > - This change accepts both labels and keeps the existing authorization checks. ## Linked Issues or Issue Description Refs #18821. Observed on #18848 in [run 41711436968](https://github.com/paperclipai/paperclip/actions/runs/41711436968). **What happened?** A CODEOWNER added `full-ci` to a trusted PR. The metadata gate ran on AWS, then sent the test matrix to GitHub with the notice `only the profile-ci label can request a profiling run`. **Expected behavior** Both supported labels keep authorized CI on AWS. A label must not grant an outside contributor access to AWS. **Steps to reproduce** Add `full-ci` to an open PR whose author and event sender satisfy the CI owner and numeric allowlist checks. Before this change, the runner decision returns `ubuntu-latest` before it checks those identities. ## What Changed - Accept `full-ci` and `profile-ci` label events in the trusted runner gate. - Test both labels with trusted users, unlisted users, and numeric-allowlisted users who are not CI owners. - Document that label selection changes CI scope and preserves runner authorization. ## Verification - The new trusted `full-ci` regression failed before the fix with `ubuntu-latest` and passes after the fix. - `node --test .github/scripts/tests/ci-routing.test.mjs .github/scripts/tests/ci-plan.test.mjs .github/scripts/tests/ci-profile.test.mjs scripts/__tests__/e2e-shard.test.mjs`: 147 passed. - `actionlint -shellcheck='' .github/workflows/ci-routing.yml`: passed. - `git diff --check`: passed. - This isolated checkout has no installed workspace dependencies. Repository-wide typecheck, tests, and build are delegated to this PR's CI; local verification covers the changed routing and its callers. - A read-only replay against this PR's real GitHub identities and commits returned GitHub with the old `full-ci` guard and AWS with the corrected guard. No PR code was executed by the replay. - Greptile reviewed current head `47f7159f67c8633830d7732cb68c6d43d8243fe5`: 5/5, successful check, no unresolved review threads. Both incremental-CI qualification jobs and commitperclip review passed. - Full CI is not green. Attempt 1 lost workers to verified Spot reclamations in `m5.xlarge/us-east-1c`. After excluding that type from new placements, one failed-job retry retained 19 successful checks and reran 27 interrupted checks on AWS. The policy check passed, including these regressions. AWS then confirmed seven more reclaimed `m8a.xlarge/us-east-1c` workers and two reclaimed `m8i-flex.xlarge/us-east-1b` workers. The affected checks show runner shutdowns; unaffected jobs remain running. [Recovery attempt](https://github.com/paperclipai/paperclip/actions/runs/41714303495/attempts/2). ## Risks This changes the event allowlist before authorization. The author, event sender, rerun actor, repository, CODEOWNERS, numeric allowlist, and immutable revision checks still apply. Unrelated labels remain denied. Separate helper workflows that explicitly choose GitHub runners are outside this change. ## Model Used OpenAI GPT-6 through Codex, with reasoning, shell, Git, and GitHub API tools. The runtime does not expose a more specific model build ID or context window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip --- .github/scripts/tests/ci-routing.test.mjs | 15 ++++++++++++++- .github/workflows/ci-routing.yml | 4 ++-- doc/DEVELOPING.md | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/scripts/tests/ci-routing.test.mjs b/.github/scripts/tests/ci-routing.test.mjs index 3b590332789..da1824cfa51 100644 --- a/.github/scripts/tests/ci-routing.test.mjs +++ b/.github/scripts/tests/ci-routing.test.mjs @@ -98,7 +98,20 @@ const stackedBase = { const routeCases = [ ["allowlisted PR", {}, awsRunner], - ["allowlisted profile label", { EVENT_ACTION: "labeled", EVENT_LABEL_NAME: "profile-ci" }, awsRunner], + ...["profile-ci", "full-ci"].flatMap((label) => { + const event = { EVENT_ACTION: "labeled", EVENT_LABEL_NAME: label }; + return [ + [`allowlisted ${label} label`, event, awsRunner], + [`${label} with unlisted author`, { ...event, EVENT_PR_AUTHOR_ID: "99999999" }], + [`${label} with unlisted sender`, { ...event, EVENT_SENDER_ID: "99999999" }], + [`${label} with unlisted rerun actor`, { ...event, TEST_TRIGGERING_ACTOR_ID: "99999999" }], + [`${label} on non-owner PR with owner sender and rerun`, { + ...event, EVENT_PR_AUTHOR_ID: "99999999", TEST_PR_AUTHOR_ID: "99999999", TRUSTED_USER_IDS: "[34892728,99999999]", + }], + [`${label} with non-owner sender`, { ...event, EVENT_SENDER_ID: "99999999", TRUSTED_USER_IDS: "[34892728,99999999]" }], + [`${label} with non-owner rerun actor`, { ...event, TEST_TRIGGERING_ACTOR_ID: "99999999", TRUSTED_USER_IDS: "[34892728,99999999]" }], + ]; + }), ["unrelated label", { EVENT_ACTION: "labeled", EVENT_LABEL_NAME: "documentation" }], ["validated stacked base", stackedBase, awsRunner], ["lagging PR API base snapshot", { TEST_PR_BASE_SHA: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, awsRunner], diff --git a/.github/workflows/ci-routing.yml b/.github/workflows/ci-routing.yml index 7fc7416f369..3afa9f0859d 100644 --- a/.github/workflows/ci-routing.yml +++ b/.github/workflows/ci-routing.yml @@ -86,8 +86,8 @@ jobs: [[ "$EVENT_BASE_REPOSITORY_ID" == '1170821064' ]] || fail_closed 'unexpected base repository ID' [[ -n "$EVENT_BASE_REF" ]] || fail_closed 'missing base branch' [[ "$EVENT_ACTION" =~ ^(opened|reopened|synchronize|labeled)$ ]] || fail_closed 'unsupported pull_request action' - if [[ "$EVENT_ACTION" == 'labeled' && "${EVENT_LABEL_NAME:-}" != 'profile-ci' ]]; then - fail_closed 'only the profile-ci label can request a profiling run' + if [[ "$EVENT_ACTION" == 'labeled' && ! "${EVENT_LABEL_NAME:-}" =~ ^(profile-ci|full-ci)$ ]]; then + fail_closed 'only the profile-ci or full-ci labels can request CI' fi jq -e ' diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 38a217d3579..7997238437e 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -164,6 +164,9 @@ history, changed PR heads and unverifiable ancestry still deny AWS execution. Missing or malformed settings and unverifiable authorization use GitHub-hosted runners. A maintainer rerunning an outside contributor's PR does not grant it AWS access. +Both `full-ci` and `profile-ci` label events use this same authorization path. +The labels request CI scope; they do not grant AWS access or move an authorized +CODEOWNER run to GitHub-hosted capacity. Other label events do not request CI. Authorized test jobs and the final `verify` and `e2e` aggregates use the selected AWS fleet. A stale admission roster can allocate only the short metadata gate; the fresh CODEOWNERS check still denies PR execution for a removed owner.