Skip to content

Commit fd93a52

Browse files
authored
Fix Drift: authenticate via copilotkit-devops-bot app token (#305)
## Problem The Fix Drift workflow was using `GITHUB_TOKEN` for every authenticated operation (git push, PR create, auto-merge). This has two critical consequences: 1. **PRs created by `GITHUB_TOKEN` don't trigger `pull_request` events** — GitHub suppresses the event to prevent recursive workflows, so drift fix PRs never get CI checks. 2. **`github-actions[bot]` can't satisfy main's approval rule** — the bot identity is not a permitted bypass actor, so even if CI somehow ran, the PR couldn't auto-merge. ## Fix Mint a copilotkit-devops-bot (app-id `1108748`) installation token early in the job using `actions/create-github-app-token`, then use that token for all three authenticated operations: - `Configure git for push` — injects the token into the git URL so the fix branch push is attributable to the bot - `Create PR` — `GH_TOKEN` is the bot token, so `gh pr create` runs as devops-bot; GitHub fires `pull_request` — CI triggers - `Auto-merge PR` — `GH_TOKEN` is the bot token; devops-bot is a scoped Integration bypass actor on main's ruleset **Required secret:** `DEVOPS_BOT_PRIVATE_KEY` must be added to the repo/org Actions secrets (being added separately). **Required ruleset entry:** devops-bot must be added as an Integration bypass actor on main's ruleset (being added separately). ## In-workflow CI green gate A ruleset bypass actor bypasses the **entire** ruleset — including any required-status-checks rule. This means a bypassing actor's PR can merge immediately without waiting for CI. To prevent merging on a red or empty build: - Poll until at least one check is registered (up to 10 x 30 s = 5 min); fail hard if none ever appear - Run `gh pr checks --watch --fail-fast` to block until all checks pass; exit non-zero kills the step - Only then call `gh pr merge --merge` (no `--auto` — we do the waiting ourselves) This makes CI-green a hard precondition enforced in the workflow, not the ruleset. ## Diff summary | Location | Change | |---|---| | After `actions/checkout` | Add `Mint app token` step (id: `app-token`) using `actions/create-github-app-token` pinned to v3.2.0 | | `Configure git for push` env | `GITHUB_TOKEN` replaced with `steps.app-token.outputs.token` | | `Create PR` env | `GITHUB_TOKEN` replaced with `steps.app-token.outputs.token` | | `Auto-merge PR` env + run | `GITHUB_TOKEN` replaced with `steps.app-token.outputs.token`; one-liner `gh pr merge --auto` replaced with poll-then-watch-then-merge guard | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 03cfa1a + f3365a6 commit fd93a52

1 file changed

Lines changed: 37 additions & 4 deletions

File tree

.github/workflows/fix-drift.yml

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ jobs:
2828
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
2929
with:
3030
persist-credentials: false
31+
- name: Mint app token
32+
id: app-token
33+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
34+
with:
35+
app-id: "1108748"
36+
private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }}
37+
permission-contents: write
38+
permission-pull-requests: write
3139
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
3240
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
3341
with:
@@ -136,14 +144,14 @@ jobs:
136144
if: steps.autofix.outcome == 'success' && success() && steps.check.outputs.skip != 'true'
137145
run: git config --local url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/"
138146
env:
139-
TOKEN: ${{ secrets.GITHUB_TOKEN }}
147+
TOKEN: ${{ steps.app-token.outputs.token }}
140148

141149
# Step 5: Create PR on success
142150
- name: Create PR
143151
id: pr
144152
if: steps.autofix.outcome == 'success' && success() && steps.check.outputs.skip != 'true'
145153
env:
146-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
154+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
147155
run: |
148156
npx tsx scripts/fix-drift.ts --create-pr
149157
PR_URL=$(gh pr list --head "$(git branch --show-current)" --json url --jq '.[0].url')
@@ -154,9 +162,34 @@ jobs:
154162
- name: Auto-merge PR
155163
if: success() && steps.pr.outputs.url != ''
156164
env:
157-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
165+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
158166
PR_URL: ${{ steps.pr.outputs.url }}
159-
run: gh pr merge "$PR_URL" --merge --auto
167+
run: |
168+
# Wait for CI to register and pass before merging. A ruleset bypass
169+
# actor bypasses the entire ruleset, so the green gate must live here,
170+
# not in branch protection. `gh pr checks --watch` blocks until all
171+
# checks complete and exits non-zero if any fail; --fail-fast bails on
172+
# first failure. Treat "no checks" as NOT-green: require at least one.
173+
#
174+
# First poll until at least one check is registered (up to ~5 min).
175+
count=0
176+
for i in $(seq 1 10); do
177+
count=$(gh pr checks "$PR_URL" 2>/dev/null | wc -l)
178+
if [ "$count" -gt 0 ]; then
179+
echo "CI checks registered ($count rows), proceeding to watch"
180+
break
181+
fi
182+
echo "No checks registered yet (attempt $i/10), waiting 30s..."
183+
sleep 30
184+
done
185+
if [ "$count" -eq 0 ]; then
186+
echo "::error::No CI checks ever registered — not merging"
187+
exit 1
188+
fi
189+
gh pr checks "$PR_URL" --watch --fail-fast --interval 30 || {
190+
echo "::error::CI checks failed or none present — not merging"; exit 1;
191+
}
192+
gh pr merge "$PR_URL" --merge
160193
161194
# Step 7: Slack notification on successful fix
162195
- name: Notify Slack on fix success

0 commit comments

Comments
 (0)