From f1e6839ddf6e1c3feef94ed737ed6232b8feea4a Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:56:06 -0400 Subject: [PATCH] ci(pr-pipeline): fund work-resolve-issue from the issue-lane subscription token work-resolve-issue passes ISSUE_LANE_CLAUDE_CODE_OAUTH_TOKEN. The write runner takes a required role input and names _CLAUDE_CODE_OAUTH_TOKEN as the lane record's credential instead of a hardcoded name; pr-refine and pr-unblock pass role: automations. Co-Authored-By: Claude Opus 5.5 --- .../ci-issue-lane-oauth-token-4e2a9c71.md | 11 +++++++++ .github/workflows/pr-refine.yml | 1 + .github/workflows/pr-run-activity-write.yml | 23 ++++++++++++++++++- .github/workflows/pr-unblock.yml | 7 ++++++ .github/workflows/work-resolve-issue.yml | 4 +++- docs/conventions/pr-pipeline/README.md | 6 +++-- 6 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 .changes/pr-pipeline/ci-issue-lane-oauth-token-4e2a9c71.md diff --git a/.changes/pr-pipeline/ci-issue-lane-oauth-token-4e2a9c71.md b/.changes/pr-pipeline/ci-issue-lane-oauth-token-4e2a9c71.md new file mode 100644 index 0000000000..ae82770299 --- /dev/null +++ b/.changes/pr-pipeline/ci-issue-lane-oauth-token-4e2a9c71.md @@ -0,0 +1,11 @@ +--- +bump: patch +--- + +### Changed + +- `pr-run-activity-write.yml` takes a required `role` input, the funding role whose OAuth token the + caller passes, and the lane record names that role's token (`_CLAUDE_CODE_OAUTH_TOKEN`) as + the run's credential instead of always `AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN`. + `work-resolve-issue` passes `issue-lane` and its own `ISSUE_LANE_CLAUDE_CODE_OAUTH_TOKEN`; + `pr-refine` and `pr-unblock` pass `automations`. diff --git a/.github/workflows/pr-refine.yml b/.github/workflows/pr-refine.yml index 138c058fc2..6a60e68c34 100644 --- a/.github/workflows/pr-refine.yml +++ b/.github/workflows/pr-refine.yml @@ -41,6 +41,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-refine activity: fix-docs pr-number: ${{ inputs.pr-number }} diff --git a/.github/workflows/pr-run-activity-write.yml b/.github/workflows/pr-run-activity-write.yml index 96b9aa6114..07cb33c4a8 100644 --- a/.github/workflows/pr-run-activity-write.yml +++ b/.github/workflows/pr-run-activity-write.yml @@ -196,6 +196,12 @@ on: required: false type: string default: "" + role: + description: >- + The funding role whose secret the caller passes, as in + docs/conventions/pr-pipeline.yaml `funding:`. + required: true + type: string secrets: claude-code-oauth-token: description: Claude Code OAuth token for skill activities. @@ -1383,6 +1389,21 @@ jobs: # ci-workflows' classifier, never from model text. The report job copies # it onto the posted check run, and issue mode writes it into lane.json, # where the limit-stop restarts read it. + # The secret the caller passed, named from its funding role: + # `issue-lane` reports ISSUE_LANE_CLAUDE_CODE_OAUTH_TOKEN. + - name: Name the run's credential + id: credential + if: always() && (steps.act-skill.outcome == 'success' || steps.act-skill.outcome == 'failure') + env: + ROLE: ${{ inputs.role }} + run: | + if [[ ! "$ROLE" =~ ^[a-z][a-z0-9-]*$ ]]; then + echo "::error::role must be lowercase letters, digits and hyphens" + exit 1 + fi + role=${ROLE^^} + echo "name=${role//-/_}_CLAUDE_CODE_OAUTH_TOKEN" >>"$GITHUB_OUTPUT" + - name: Record the lane outcome id: lane-record if: always() && (steps.act-skill.outcome == 'success' || steps.act-skill.outcome == 'failure') @@ -1393,7 +1414,7 @@ jobs: ${{ steps.act-skill.outputs.execution_file || format('{0}/claude-execution-output.json', runner.temp) }} lane: ${{ inputs.lane }} - credential: AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN + credential: ${{ steps.credential.outputs.name }} max-turns: ${{ steps.config.outputs.max-turns }} - name: Upload the skill's final reply diff --git a/.github/workflows/pr-unblock.yml b/.github/workflows/pr-unblock.yml index 09ddfbe310..df9aa5d65d 100644 --- a/.github/workflows/pr-unblock.yml +++ b/.github/workflows/pr-unblock.yml @@ -69,6 +69,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: resolve-conflicts pr-number: ${{ inputs.pr-number }} @@ -91,6 +92,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: address-feedback pr-number: ${{ inputs.pr-number }} @@ -117,6 +119,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: publish-feedback pr-number: ${{ inputs.pr-number }} @@ -197,6 +200,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: review-dependency pr-number: ${{ inputs.pr-number }} @@ -218,6 +222,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: apply-dependency-verdict pr-number: ${{ inputs.pr-number }} @@ -351,6 +356,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: apply-ci-fix pr-number: ${{ inputs.pr-number }} @@ -428,6 +434,7 @@ jobs: queue: max uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: + role: automations lane: pr-unblock activity: report-escalation pr-number: ${{ inputs.pr-number }} diff --git a/.github/workflows/work-resolve-issue.yml b/.github/workflows/work-resolve-issue.yml index 5f9a32aac2..574ee49cc8 100644 --- a/.github/workflows/work-resolve-issue.yml +++ b/.github/workflows/work-resolve-issue.yml @@ -118,6 +118,7 @@ jobs: uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: lane: work-resolve-issue + role: issue-lane activity: resolve-issue issue-number: ${{ format('{0}', github.event.issue.number) }} trusted-base: default-tip @@ -125,7 +126,7 @@ jobs: timeout-minutes: ${{ fromJSON(needs.pick-slot.outputs.timeout-minutes || '25') }} issue-dimensions: ${{ needs.pick-slot.outputs.issue-dimensions }} secrets: - claude-code-oauth-token: ${{ secrets.AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN }} + claude-code-oauth-token: ${{ secrets.ISSUE_LANE_CLAUDE_CODE_OAUTH_TOKEN }} # One `pipeline/` commit status per lane step on the lane branch's # head (post-statuses.mjs). No step is skipped on them; a failing one holds @@ -323,6 +324,7 @@ jobs: uses: ./.github/workflows/pr-run-activity-write.yml # zizmor: ignore[self-repository] with: lane: work-resolve-issue + role: issue-lane activity: report-outcome issue-number: ${{ format('{0}', github.event.issue.number) }} trusted-base: default-tip diff --git a/docs/conventions/pr-pipeline/README.md b/docs/conventions/pr-pipeline/README.md index 1f8d2ac08b..e175cc6bf4 100644 --- a/docs/conventions/pr-pipeline/README.md +++ b/docs/conventions/pr-pipeline/README.md @@ -1055,8 +1055,10 @@ its role and secrets, and the runner policy admits them. This repository's map: `claude-security-review` take `reviews`; `work-resolve-issue` takes `issue-lane`. `intake` and `pr-verify` run on `run-claude-cli` today. Every other entry is the role its lane -takes when it moves there: until then it runs claude-code-action with an OAuth token alone, and it -never gets an API key, since claude-code-action runs are not paid from API credits. +takes when it moves there: until then it runs claude-code-action with its role's OAuth token alone, +and it never gets an API key, since claude-code-action runs are not paid from API credits. A lane +on pr-run-activity-write.yml passes its role as the `role` input, and the lane record names that +role's token as the run's credential. ## Loop caps