Skip to content

ci: harden dependabot workflows and switch Claude fix to workflow_run#348

Merged
umair-ably merged 2 commits into
mainfrom
ci/skip-claude-review-dependabot-ignore-scripts
Apr 17, 2026
Merged

ci: harden dependabot workflows and switch Claude fix to workflow_run#348
umair-ably merged 2 commits into
mainfrom
ci/skip-claude-review-dependabot-ignore-scripts

Conversation

@umair-ably

@umair-ably umair-ably commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Three changes to how our CI handles dependabot PRs:

  1. claude-review.yml — exclude PRs opened by dependabot[bot] from the auto-review job. Explicit-mention paths (@claude comment, review-requested, etc.) are unchanged, so maintainers can still ask for a review on a tricky bump.
  2. dependabot-lockfile.yml → renamed to Regenerate Dependabot Lockfile. Its only job now is regen-lockfile (regenerate pnpm-lock.yaml and push). The 25-minute polling loop and Claude fix step are removed from this workflow. The pnpm install in the regen step gains --ignore-scripts.
  3. dependabot-claude-fix.yml (new) — Fix Dependabot PRs. Fires on workflow_run.completed for Run Tests, E2E Tests, Web CLI E2E Tests (Parallel), and Security Audit. Each event is a gate check: computes which of those workflows were expected for this PR (based on each workflow's on.pull_request.paths filter vs the PR's changed files), then checks whether every expected one has completed on this HEAD SHA. If any is still queued/running, exits 0 — the next workflow_run event will re-gate. When the last expected workflow finishes, aggregates the full failure set and runs Claude once.

Motivation

Skip auto-review on dependabot PRs. GitHub runs workflows triggered by dependabot under a restricted permission scope — the GITHUB_TOKEN is read-only and repo secrets are not exposed to the workflow. claude-review.yml needs secrets.ANTHROPIC_API_KEY, secrets.CLAUDE_APP_ID, and secrets.CLAUDE_APP_PRIVATE_KEY to run; on a dependabot PR those resolve to empty strings and the job fails outright. Gating the job off for user.login == 'dependabot[bot]' stops the workflow from running at all (rather than running, failing, and showing a red ❌ on every dependabot PR). The mention paths are kept intact: @claude in a review comment re-runs the workflow under an issue_comment/pull_request_review_comment event, which doesn't inherit the restricted dependabot scope, so a maintainer can still request a review on a tricky bump.

--ignore-scripts during lockfile regen. The regen step installs dependencies from a dependabot-authored PR — a freshly-bumped set of versions nobody has reviewed. Any package's postinstall would otherwise execute with the workflow's App token (which can push back to the PR branch). --ignore-scripts eliminates that arbitrary-code-execution surface during regen. Real builds/tests run in subsequent workflows where scripts execute normally.

Replace the polling loop with a workflow_run-gated fix job. The previous fix-failures job polled for up to 25 minutes every time a dependabot PR moved, even when every check passed outright. It then completed successfully with nothing to do, and that "empty success" became the latest row in the Actions tab — overwriting the earlier run where Claude actually did migration work and making those logs harder to find.

The new shape solves both problems:

  • No runner time burned on green dependabot PRs. The new workflow only runs when some upstream workflow failed (workflow_run fires for all outcomes, but the gate collapses to a no-op for an all-green set).
  • Actions tab history is self-describing. Because the new workflow only appears in history when it had work to do, every row in its history represents an actual fix attempt — no more clicking back through empty successes to find the migration run.
  • Claude still sees every failure at once. The gate deliberately waits for every expected workflow before proceeding, so one Claude invocation handles the full set. Matches the old polling behaviour.

How the gate works

On each workflow_run.completed event:

  1. Identify PR + confirm dependabot author. github.event.workflow_run.pull_requests[0].number → PR → .user.login == 'dependabot[bot]'. PRs from forks (empty pull_requests array) and non-dependabot PRs exit 0. The PR author is stable across lockfile-regen pushes (those are attributed to the App, not the PR opener).
  2. Loop guard. Caps successful runs per branch at 20, same shape as the regen workflow's guard.
  3. Compute expected workflows for this PR. For each monitored workflow, check its on.pull_request.paths (if any) against the PR's changed files via a small shell matcher that handles exact paths and DIR/** globs. Run Tests is paths-filtered; the other three have no filter and are always expected.
  4. Query /actions/runs?head_sha=... and evaluate per expected workflow. If any is still queued/in_progress, or hasn't produced a run yet, exit 0 — the next event re-gates. If all completed, proceed to aggregation.
  5. Aggregate failures. Pull gh run view --log-failed for each failed run, feed them into the existing Claude prompt as one combined input.

Concurrency is keyed on head_sha with cancel-in-progress: true. Near-simultaneous completions may produce two events that both see "all complete" — the second cancels the first and we get one surviving Claude run per SHA. New pushes (Claude's own fix commit, a fresh dependabot commit) get their own lane.

Note: the fix workflow uses workflow_run, which runs with full default-branch permissions (including secrets) regardless of whether the triggering event was a dependabot PR. That's specifically what lets us run Claude + push fixes back to a dependabot branch — something the standard pull_request trigger can't do for dependabot-authored PRs because of the restricted scope described above.

Trade-offs to be aware of

  • workflow_run always uses the default-branch copy of the workflow file. Once merged, prompt edits take effect on subsequent runs, not on runs inside the PR that edits them.
  • Paths list for Run Tests is duplicated in the gate step. Kept in sync with test.yml's on.pull_request.paths via a comment. If any other monitored workflow adds a paths filter, extend the matcher block.
  • The Claude prompt is preserved verbatim; only the failure inputs and the gh pr comment PR number reference are rewired from the old steps.wait-for-checks.outputs.* to steps.gate.outputs.* / steps.pr.outputs.pr_number.

Test plan

  • On the next dependabot PR, confirm Claude PR Review does not run on open (no red ❌ from a secrets-starved job).
  • On the same PR, @claude in a comment → confirm the review runs via the mention path (workflow runs with full secrets under issue_comment).
  • Trigger a dependabot bump that touches only package.json/pnpm-lock.yaml: confirm Regenerate Dependabot Lockfile runs with --ignore-scripts and commits the regen.
  • After the regen pushes a new commit: verify Fix Dependabot PRs fires on the last upstream CI workflow's completion (not on each one) and that its Actions-tab row only appears when at least one upstream workflow failed.
  • Introduce a contrived failing test in a dependabot PR, confirm Claude sees failures from multiple workflows in one invocation (single Fix Dependabot PRs run, combined failure_logs payload).
  • Confirm that on an all-green dependabot PR, no Fix Dependabot PRs row is created.

@vercel

vercel Bot commented Apr 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Apr 17, 2026 9:08am

Request Review

@claude-code-ably-assistant

Copy link
Copy Markdown

Walkthrough

This PR hardens two CI workflows that interact with Dependabot PRs. It prevents the Claude auto-review job from running on routine Dependabot bumps (reducing noise and API spend), and adds --ignore-scripts to the lockfile-regeneration pnpm install step to eliminate an arbitrary-code-execution surface during dependency installation from unreviewed branches.

Changes

Area Files Summary
Config .github/workflows/claude-review.yml Skip auto-review job when the PR author is dependabot[bot]; explicit @claude-mention paths remain active
Config .github/workflows/dependabot-lockfile.yml Add --ignore-scripts to pnpm install during lockfile regeneration to prevent untrusted postinstall hooks from running

Review Notes

  • Security hardening: The --ignore-scripts change is a supply-chain safety measure — without it, a malicious or compromised package's postinstall hook could execute with the workflow's GitHub token (which has push access to the PR branch). Low risk of breakage since the lockfile-regen step doesn't need build scripts.
  • Behavioral change: Dependabot PRs will no longer receive an automatic Claude review. Maintainers can still trigger one via @claude mention if a bump warrants closer inspection.
  • No code changes: Both files are workflow-only; no source, tests, or dependencies are affected.
  • No migration needed: Changes take effect on the next workflow run with no manual steps required.

@claude-code-ably-assistant claude-code-ably-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: ci: skip Claude review on dependabot PRs and harden lockfile regen

Both changes are correct and well-motivated. No issues to flag.

.github/workflows/claude-review.yml

The operator precedence is correct. The dependabot[bot] exclusion is added inside the first && group (already parenthesised), so it only applies to the pull_request event path. The explicit-mention || branches (issue_comment, pull_request_review_comment, assignee/review-requested) are unchanged — maintainers can still request a Claude review on any dependabot PR by @claude-mentioning. The bot username dependabot[bot] is the correct GitHub identity for Dependabot.

.github/workflows/dependabot-lockfile.yml

--ignore-scripts and --frozen-lockfile are orthogonal and compose correctly. The security rationale is sound: this install step runs against package versions a maintainer hasn't yet reviewed, so skipping postinstall hooks eliminates the supply chain risk of arbitrary code executing with the workflow's push-capable GitHub token. Build/test scripts run as normal in subsequent workflow runs after the lockfile commit.

Overall

Clean, minimal, well-scoped. Both changes do exactly what the PR description says.

@umair-ably umair-ably changed the title ci: skip Claude review on dependabot PRs and harden lockfile regen ci: harden dependabot workflows and switch Claude fix to workflow_run Apr 17, 2026
Two small hardening changes to the CI workflows that touch dependabot:

- claude-review.yml: exclude PRs opened by dependabot[bot] from the
  auto-review job. Dependabot PRs are routine lockfile/version bumps
  — Claude review on them is noise, delays merge, and consumes API
  budget with no meaningful signal. The explicit-mention paths
  (issue_comment, pull_request_review_comment, etc.) are unchanged,
  so a maintainer can still request a review on a dependabot PR by
  @-mentioning Claude.

- dependabot-lockfile.yml: pass --ignore-scripts to pnpm install in
  the lockfile-regeneration step. This step installs dependencies
  from a dependabot-authored PR, so any freshly-bumped package's
  postinstall hook would otherwise run with the workflow's token
  (including permissions to push to the PR branch). --ignore-scripts
  eliminates that arbitrary-code-execution surface during the regen.
  Build scripts on our own code aren't needed here — the step only
  resolves/installs to produce a consistent lockfile.
Replaces the 25-minute polling loop in dependabot-lockfile.yml with an
event-driven workflow that fires only after upstream CI finishes —
and only when there's actually something to fix. Also drops the
"empty success" run that was overwriting Claude's fix run in the
Actions tab history.

Architecture (two workflows, one purpose):

1. dependabot-lockfile.yml (renamed to "Regenerate Dependabot Lockfile"):
   regenerates pnpm-lock.yaml on dependabot PRs and pushes the update.
   Nothing else. The fix-failures job and its 25-minute wait-for-checks
   polling loop are gone.

2. dependabot-claude-fix.yml (new, "Fix Dependabot PRs"): fires on
   workflow_run.completed for the four CI workflows we care about
   — Run Tests, E2E Tests, Web CLI E2E Tests (Parallel), Security
   Audit. Each firing is a gate check, not an immediate trigger:

   a. Look up the PR the triggering workflow ran on. Exit if the
      author isn't dependabot[bot] or the PR is from a fork.
   b. Loop-prevention guard (≤20 successful runs per branch), same
      shape as the lockfile workflow's guard.
   c. Compute expected workflows dynamically for this PR — check
      the PR's changed files against each workflow's
      on.pull_request.paths filter. Run Tests has a paths list;
      the other three always run.
   d. Gate: for every expected workflow, check its run status on
      this HEAD SHA. If any is still queued/running, or hasn't
      produced a run record yet, exit 0. A later workflow_run event
      (from whichever upstream finishes next) will re-run the gate.
   e. When the gate opens (last expected workflow has finished),
      gather the failures from all of them in one pass, feed the
      aggregated summary + logs into the existing Claude prompt,
      and run the fix step.

   Concurrency is keyed on HEAD SHA with cancel-in-progress=true:
   near-simultaneous completions can fire two workflow_run events
   that both see "all complete"; the second cancels the first and
   we get a single Claude run per SHA. New pushes (Claude's fix,
   another dependabot commit) get their own concurrency lane.

Why this shape:

- No runner time burned on green dependabot PRs. The old workflow
  polled for up to 25 minutes on every PR, even when CI passed
  outright.
- Actions tab history is self-describing. The old fix-failures job
  always ran to completion and usually succeeded with nothing to do,
  so the most recent "Fix Dependabot PRs" row in the Actions tab
  was typically an empty success, hiding the earlier run that
  actually did the migration work. Now the workflow only runs when
  there's something to fix, so every row in its history represents
  real work.
- Claude still sees all failures at once. The gate deliberately
  waits for every expected workflow before proceeding, so the
  single Claude invocation handles the full failure set — matching
  the behaviour of the old polling loop.

Implementation notes:

- The "Run Tests" paths list is duplicated in the gate step (kept
  in sync with test.yml on.pull_request.paths via a comment). The
  matcher handles exact paths and DIR/** globs, which covers every
  pattern this repo currently uses.
- workflow_run always uses the default-branch version of the
  workflow file; prompt edits take effect on subsequent runs after
  merge, not in the PR that edits them.
- workflow_run-triggered jobs can use write tokens, so the App
  token flow for pushing Claude's fix commits works the same as
  before.
- The Claude prompt is preserved verbatim except for the failure
  inputs (now come from steps.gate.outputs instead of
  steps.wait-for-checks.outputs) and the PR number reference for
  the final gh pr comment call.
@umair-ably
umair-ably force-pushed the ci/skip-claude-review-dependabot-ignore-scripts branch from e3b0b54 to 7d94a81 Compare April 17, 2026 09:08
@umair-ably
umair-ably merged commit 61d0ac5 into main Apr 17, 2026
5 of 6 checks passed
@umair-ably
umair-ably deleted the ci/skip-claude-review-dependabot-ignore-scripts branch April 17, 2026 09:08
@emptyhammond
emptyhammond requested a review from AndyTWF June 26, 2026 15:54
@fixlyai

fixlyai commented Jul 23, 2026

Copy link
Copy Markdown

You've now hardened dependabot-claude-fix.yml through three substantial rewrites (#333, #348). Each rewrite fixes real problems. but as far as I can tell there's no regression harness between rewrites, so every change risks silently undoing a behavior the previous round fixed. That's a rough spot to be in with a workflow that has contents:write and runs multiple times a day.

We maintain an open-source tool (reelier, github.com/seldonframe/reelier) built for this: record a known-good agent fix run once, then replay it on every workflow change, deterministic steps replay at zero tokens, and fail CI when the run's behavior drifts from the recording. It effectively gives the workflow itself a test suite.

Disclosure: I'm a maintainer, so treat this as a biased suggestion from someone who thinks your setup is one of the most advanced agent-in-CI deployments around. If it's interesting, I'm happy to open a PR with the recording + workflow wired up so the cost to evaluate is one review. If not, please feel free to close this. no follow-up from me either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants