Skip to content

feat(dependabot-auto-merge): convert to reusable workflow, add #64 guardrails - #96

Merged
twistedmelonman merged 3 commits into
mainfrom
claude/reusable-dependabot-auto-merge-a2
Aug 7, 2026
Merged

feat(dependabot-auto-merge): convert to reusable workflow, add #64 guardrails#96
twistedmelonman merged 3 commits into
mainfrom
claude/reusable-dependabot-auto-merge-a2

Conversation

@twistedmelonman

Copy link
Copy Markdown
Member

Summary

Implements Part A2 of the reusable-workflows plan: extracts dependabot-auto-merge.yml from a copy-pasted-per-repo file into a workflow_call reusable workflow, matching the pattern already used by claude-blocking-review and claude-assistant.

Closes #87 (structural half — approve/merge calls now degrade to ::warning:: instead of hard-failing) and #64 (no-checkout guardrail, enforced two ways).

What changed

  • New file: .github/workflows/dependabot-auto-merge.yml — the reusable workflow. Preserves the currently-deployed working logic exactly (env: GH_TOKEN, env: PR_URL, id: metadata), plus:
    • A "Verify caller permissions" preflight (mirrors claude-blocking-review.yml's pattern) that fails loudly if the caller didn't grant contents: write / pull-requests: write.
    • Both gh pr review --approve and gh pr merge --auto now degrade to ::warning:: on failure (the original only fixed the approve call — see dependabot-auto-merge is inert in 29/51 fleet repos: can_approve_pull_request_reviews unset by installer #87).
    • Header comment documents the no-checkout invariant (Add guardrail preventing actions/checkout in dependabot-auto-merge.yml #64) and an explicit "callers MUST NOT use secrets: inherit" rule with the reasoning (splitting this file out of every caller repo creates a new incentive to reach for secrets: inherit on an auth failure, which would leak every repo secret to code evaluating externally-authored PR content under pull_request_target).
    • dependabot/fetch-metadata pinned to its v2 commit SHA (matching this repo's convention for actions/checkout).
  • .github/workflows/self-review.yml: new guard-no-checkout job — plain shell, no LLM call, so it isn't subject to the workflow-self-modification skip. Fails if this repo's own dependabot-auto-merge.yml ever contains actions/checkout. (Moved the top-level permissions: block down onto the claude-review job since the new job needs a much smaller set — zizmor's excessive-permissions check was right that the old top-level grant was overly broad for the new job mix.)
  • claude-review-audit.sh: new read-only, audit-only check — for every repo it scans, flags any caller stub referencing dependabot-auto-merge.yml that contains actions/checkout or secrets: inherit. Doesn't block or gate anything, consistent with the rest of the script.
  • README.md: new ## dependabot-auto-merge section documenting setup, the secrets: inherit prohibition, the no-checkout invariant, tag versioning, and rollout discipline. (Also picked up a markdownlint table-formatting fix on adjacent pre-existing tables — pre-existing en-dash unicode character was breaking MD060 alignment checks; switched to plain hyphens.)

Versioning

Tagged with a prefixed namespacedependabot-auto-merge-v1, dependabot-auto-merge-v1.0.0 — not bare v1. Git tags are repo-scoped; v1 already exists in this repo and is live, consumed by claude-assistant.yml@v1. Tag/release creation is deferred until after merge, per this repo's established convention (confirmed in the recent A1 PR).

Known findings, tracked as follow-ups (not blocking this PR)

  • Permission preflight false-positive when PR_NUMBER is empty #94: the permission preflight's gh api repos/.../pulls/${PR_NUMBER} check has a false-positive if PR_NUMBER is empty (non-pull_request-family caller trigger) — degrades to a silent no-op rather than a loud diagnostic. Bounded blast radius (caller misconfiguration only; the documented caller stub uses the correct trigger).
  • Audit script flags actions/checkout in comments of caller stubs #95: the audit script's new actions/checkout / secrets: inherit grep doesn't strip comments first, so a caller stub with an explanatory comment mentioning either string would false-positive in the audit report. Audit-only (no blocking/gating), so blast radius is report noise.
  • zizmor flags if: github.actor == 'dependabot[bot]' as a spoofable bot-conditions finding (general recommendation: github.event.pull_request.user.login). Preserved verbatim per the plan and issue Add guardrail preventing actions/checkout in dependabot-auto-merge.yml #64's own audit — the code-execution-via-spoofed-actor bypass zizmor describes requires a checkout step, which this workflow structurally never has. Noted in the commit message rather than silently suppressed or redesigned.

Follow-up work outside this PR

  • The template repos (smartwatermelon/.github, nightowlstudiollc/.github) need their workflow-templates/dependabot-auto-merge.yml replaced with a caller stub once this PR merges and the dependabot-auto-merge-v1 tag is cut — separate PRs, in progress.
  • Per the plan's rollout discipline: point 2-3 low-traffic pilot repos at the new tag explicitly (not floating) first, and let a real Dependabot PR flow through each before repointing any floating tag fleet-wide.
  • Tag/release creation (dependabot-auto-merge-v1, dependabot-auto-merge-v1.0.0) is deferred until after this PR merges.

Test plan

  • shellcheck -S info claude-review-audit.sh clean
  • yamllint clean (only pre-existing-style line-length warnings)
  • markdownlint clean
  • Local code-reviewer + adversarial-reviewer: PASS
  • Pre-push full-diff + whole-codebase review: PASS (2 non-blocking issues filed as Permission preflight false-positive when PR_NUMBER is empty #94, Audit script flags actions/checkout in comments of caller stubs #95)
  • Manual: on a pilot repo pinned to the new tag, confirm a real/simulated Dependabot patch/minor PR completes the full chain, and a major-version PR is correctly left unmerged (deferred to post-merge pilot rollout per plan)
  • Manual: add actions/checkout to a scratch-branch copy of dependabot-auto-merge.yml and confirm guard-no-checkout fails

Claude-Session: https://claude.ai/code/session_0143Nf4sXqnTp2Kx3LNbMvBA

…ardrails

Extracts dependabot-auto-merge.yml into a workflow_call reusable, matching
the pattern already used by claude-blocking-review and claude-assistant.
Preserves the currently-deployed working logic exactly (env: GH_TOKEN,
env: PR_URL, id: metadata) plus two hardening changes:

- Both gh pr review --approve and gh pr merge --auto now degrade to a
  visible ::warning:: instead of a hard failure (closes the structural
  half of #87 — a hard failure on either call left an unactionable red
  check with no visible cause).
- A "Verify caller permissions" preflight, mirroring
  claude-blocking-review.yml's pattern, fails loudly if the caller didn't
  grant contents:write / pull-requests:write.

Tagged with a prefixed namespace (dependabot-auto-merge-v1, not v1) since
bare v1/v2/v3 tags in this repo are already live and consumed by
claude-assistant.yml@v1.

Adds the #64 no-checkout guardrail in two places: a new plain-shell
guard-no-checkout job in self-review.yml (separate from the claude-review
job, which is a reusable-workflow call and can't have its own steps; also
deliberately not an LLM-based check, so it isn't subject to the
workflow-self-modification skip that would otherwise exempt exactly the
PRs that need checking), and a read-only fleet-wide check in
claude-review-audit.sh that flags any caller stub containing
actions/checkout or secrets: inherit.

Documents the "callers MUST NOT use secrets: inherit" rule in both the
workflow's header comment and README.md.

Note for reviewers: zizmor flags dependabot-auto-merge.yml's
`if: github.actor == 'dependabot[bot]'` as a spoofable bot-conditions
finding (its general recommendation is
github.event.pull_request.user.login instead). This condition is
preserved verbatim per the plan and issue #64's own audit, which argues
it's safe here specifically because the workflow only ever makes API
calls (fetch-metadata, gh pr review/merge) and never executes PR code —
the code-execution-via-spoofed-actor bypass zizmor describes doesn't
apply without a checkout step. Flagging explicitly rather than silently
suppressing or redesigning the condition.

Claude-Session: https://claude.ai/code/session_0143Nf4sXqnTp2Kx3LNbMvBA
twistedmelonman and others added 2 commits August 7, 2026 16:04
…ment

The guardrail's bare `grep -q "actions/checkout"` matched the header
comment in dependabot-auto-merge.yml that explains why actions/checkout
must never be added there, failing the job on the very PR meant to add
that comment. Strip comment lines and match the real `uses:` invocation
form instead of a bare substring.

Also fixes two pre-existing zizmor findings on the guardrail's own
checkout step: missing persist-credentials: false, and a version
comment that said v4 instead of the actually-pinned v4.3.1.
@twistedmelonman
twistedmelonman merged commit 2e8dce6 into main Aug 7, 2026
4 checks passed
twistedmelonman added a commit to smartwatermelon/.github that referenced this pull request Aug 7, 2026
…perties (#11)

* feat(workflow-templates): add dependabot-auto-merge caller stub + properties

Replaces the self-contained dependabot-auto-merge.yml body with a caller
stub pointing at the new reusable workflow in
smartwatermelon/github-workflows (Part A2 of the reusable-workflows plan;
see smartwatermelon/github-workflows#96). Adds the missing
dependabot-auto-merge.properties.json so the workflow-templates picker
actually offers this template — it was confirmed silently absent from the
picker during planning validation.

Temporarily pinned to a commit SHA rather than the dependabot-auto-merge-v1
tag, since that tag doesn't exist until github-workflows#96 merges and the
tag is cut (~1 minute after merge, per this fleet's established
convention). Follow-up PR needed once the tag exists.

Claude-Session: https://claude.ai/code/session_0143Nf4sXqnTp2Kx3LNbMvBA

* chore(workflow-templates): repoint dependabot-auto-merge caller stub to cut tag

The reusable workflow's dependabot-auto-merge-v1.0.0 tag (and floating
dependabot-auto-merge-v1) are now cut in smartwatermelon/github-workflows
following #96's merge. Replace the temporary branch-tip SHA pin with the
real tag.

* chore(workflow-templates): repoint dependabot-auto-merge stub to cut tag

The reusable workflow's dependabot-auto-merge-v1.0.0 tag (and floating
dependabot-auto-merge-v1) are now cut in smartwatermelon/github-workflows
following #96's merge. Replace the temporary branch-tip SHA pin with the
real floating tag, and document explicitly that floating (versus a
stricter pin) is an accepted tradeoff for this template, not an
oversight -- matching the precedent already set by
claude-blocking-review.yml@v3.

* docs(workflow-templates): note actor-gate and defaults live in the reusable

Address two non-blocking review findings on the prior commit: the actor
gate (dependabot[bot] only) and merge-scope defaults are entirely inside
the reusable workflow now, invisible from this caller stub since it
passes no with: inputs. Point auditors at the reusable workflow file
directly.

---------

Co-authored-by: Claude Code Bot <claude-code@smartwatermelon.github>
@twistedmelonman
twistedmelonman deleted the claude/reusable-dependabot-auto-merge-a2 branch August 8, 2026 01:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dependabot-auto-merge is inert in 29/51 fleet repos: can_approve_pull_request_reviews unset by installer

1 participant