Opt-in via new 'dependabot-auto-approve' input (mode 'dev-patch-minor').
When enabled, Dependabot PRs are evaluated on pull_request_target
opened/reopened and on check_suite completed for dependabot/ branches,
and approved + labeled + auto-merged only when ALL gates hold:
1. Author is the real dependabot[bot] app (login AND user id 49699333)
2. Same-repo head on a dependabot/ branch (never forks)
3. Every commit signature-verified and dependabot-authored
4. Changed files a subset of 'dependabot-allowed-files' (exact paths)
5. Every updated dependency direct:development with semver-patch/minor
update per Dependabot's commit metadata (missing metadata = no approve)
6. CI green on head SHA: all check runs success/neutral/skipped with at
least one non-skipped check, combined status success if any exist
7. PR open, non-draft, conflict-free, not on hold
Every gate fails closed, logging the reason to the action log only.
Approvals use a distinct marker (slash-commands-dependabot-auto) that
push invalidation now dismisses alongside /lgtm approvals; after a human
push, gate 3 fails permanently so only /lgtm can accept the PR.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ygNtiNcAaqTRnQjHCDkVS
When the approved label was most recently applied directly by a member of
always-allow-teams (i.e. a core maintainer using the GitHub UI) rather than by
the bot via /lgtm, treat it as a sticky governance decision and skip push
invalidation entirely. The bot-applied case (per-SHA /lgtm approval) still
invalidates as before.
Adds an invalidate-skipped result value and updates docs.
🏠 Remote-Dev: homespace
Add project-gate-label input. When set, the project status update on
/lgtm accept only runs if the PR carries that label — lets callers
restrict project-board tracking to a subset of PRs (e.g. only SEPs go
to the SEP Review Pipeline, not every accepted PR).
onAccept now takes the full PR object instead of (number, nodeId) so it
can inspect labels without a second fetch.
Label removal via remove-labels-on-accept is unchanged — it runs on
every accept regardless of the gate.
🏠 Remote-Dev: homespace
When /lgtm (or /lgtm force) accepts a PR:
- Remove configurable staging labels (remove-labels-on-accept input,
e.g. in-review,draft,proposal). Missing labels are ignored via the
existing 404-tolerant removeLabel helper.
- Move the PR to a configured status in an org-level Project V2
(project-number / project-status-field / project-accepted-option
inputs). Resolves project, field, and option IDs via GraphQL, adds
the PR to the project if not already present (addProjectV2ItemById
is idempotent), then sets the single-select field. All optional —
project update is skipped when project-number is empty.
Requires the GitHub App to have Organization Projects:write for the
project update. Failures are non-fatal (warned, not thrown) so a
misconfigured project doesn't block the approval.
🏠 Remote-Dev: homespace
When branch protection enables require_code_owner_review, the GitHub
App's APPROVE review does not satisfy it (Apps cannot be team members).
Auto-merge also does not inherit the App's ruleset-bypass privileges,
so the PR sits in BLOCKED/REVIEW_REQUIRED forever with auto-merge
enabled but never firing.
Detect this by reading reviewDecision back from the
enablePullRequestAutoMerge mutation. If it is still REVIEW_REQUIRED
after we submitted an approval, attempt a direct merge instead — a
direct merge does use the App's bypass.
Both direct-merge paths (this new one and the existing clean-status
fallback) now go through a shared mergeDirect helper that catches
failures and emits an actionable warning pointing at the ruleset
bypass-list setup.
🏠 Remote-Dev: homespace
The two-token split existed because the old design used GITHUB_TOKEN for
user-visible writes (so they show as github-actions[bot]) and a separate
PAT with read:org for the team check. With the GitHub App flow, one App
installation token covers both roles.
- Drop the bot-token input; github-token now handles all API calls
- Replace the raw-fetch team check with github.rest.teams.getMembershipForUserInOrg
— the main Octokit client is now authed with the same token
- Remove the ORG_TOKEN env plumbing and the explanatory comment about
why we couldn't use Octokit before
The merge gate is now solely the App's APPROVE review satisfying branch
protection's required-reviews rule, plus auto-merge. The commit-status
mechanism is removed entirely:
- delete slash-commands/status sub-action
- drop status-context input and setCommitStatus() from main.js
- drop the now-unused hadApproved/hadHold label snapshot
README rewritten to document the GitHub App setup (MCP_COMMANDER_APP_ID
+ MCP_COMMANDER_APP_KEY secrets, actions/create-github-app-token to mint
the installation token) as the primary flow. The App token is passed as
both github-token (team checks) and bot-token (approval/auto-merge) so
approvals come from the App identity and always count toward required
reviews, independent of the "Allow GitHub Actions to approve" repo
setting.
/lgtm now only accepts members of always-allow-teams (core-maintainers
by default) — CODEOWNERS no longer grants /lgtm authorization. /hold
and /stageblog keep the team-or-CODEOWNERS rule.
On a successful /lgtm the action now:
- adds the accepted label (renamed from approved)
- submits the bot APPROVE review
- enables auto-merge (new)
Auto-merge is disabled on /lgtm cancel and on push invalidation. If the
PR is already mergeable when auto-merge is enabled, the action merges
directly instead.
This shifts the merge gate from the slash-commands/approval commit
status to GitHub's native required-reviews + auto-merge. The commit
status is now optional (set status-context to empty to skip). The
status sub-action is documented as optional for callers who want to
keep status-check-based gating.
New inputs: enable-auto-merge (default true), auto-merge-method
(default squash).
Adds a /lgtm force variant gated to force-allow-teams (default:
core-maintainers,lead-maintainers). Bypasses the self-approval guard
and the CODEOWNERS path — only team membership grants it. The bot
review body records that force was used, and the result output is
lgtm-forced (or force-unauthorized on denial).
/hold force and /stageblog force are rejected as noop rather than
silently treated as the base command.
/lgtm was adding the approved label and posting the bot review, but the
merge-gate commit status stayed pending forever. Root cause: the status
sub-action was designed to react to the labeled/unlabeled events emitted
when main.js mutates labels, but main.js writes via GITHUB_TOKEN and GitHub
suppresses workflow runs for GITHUB_TOKEN-triggered events. The event
cascade never happened.
Fix: main.js now calls createCommitStatus directly after each of the four
label mutations (/lgtm, /lgtm cancel, /hold, /unhold), computing the new
state from the pre-mutation label snapshot so cross-label cases (lgtm while
held, unhold while approved) resolve correctly.
The status sub-action is reduced to setting pending on open and on push.
It no longer reads the approved label at all — that branch is removed, and
the approved-label input with it. This closes a bypass: previously anyone
with triage permissions could manually add the approved label, and on the
resulting labeled event the sub-action would set success. Now the only
success writer is the auth-gated /lgtm path.
Caller workflows should trim pull_request_target types to [opened,
synchronize]. The labeled/unlabeled/reopened triggers are dead weight:
labeled/unlabeled never fired for bot writes, and reopened would overwrite
a valid success with pending (commit statuses persist on the SHA across
close/reopen).
The previous attempt (constructing orgClient via require('@actions/github')
inside the inline script) fails with MODULE_NOT_FOUND — the package is
bundled into github-script's dist/index.js and not resolvable even from
the eval'd inline script.
Switch to a raw fetch() call for the team membership endpoint. Node 20
(github-script@v8) has global fetch, so this has zero dependencies and
is immune to Octokit's auth-hook override behavior.
@actions/github is only resolvable from inside the github-script bundle,
not from main.js. Build the PAT-authed Octokit client in action.yml and
pass it into main as orgClient. Switch the team-membership check to use
orgClient.rest.teams.getMembershipForUserInOrg instead of a raw request
with header override.
PR #5 broke the action in production — the require() fails with
MODULE_NOT_FOUND because actions/github-script bundles @actions/github
into dist/index.js; it's not in node_modules and not resolvable from
a sibling require()'d file.
Fix: flip the architecture. github-script's 'github' client now uses
bot-token (GITHUB_TOKEN), so the single 'github' object serves all
user-visible operations. The PAT is passed via env (ORG_TOKEN) and
used ONLY for the team-membership call via github.request() with an
explicit authorization header override.
Side effect: PAT requirements shrink further. It now ONLY needs
Organization Members:read — no repo perms, no Actions:write. All
repo-level operations (including workflow dispatch) use GITHUB_TOKEN
with the workflow's permissions: block.
Previously the action used the PAT (SLASH_COMMANDS_TOKEN) for everything,
so comments and reviews showed as authored by whoever owns the PAT. This
is confusing — the PAT exists only because team-membership checks need
read:org, which GITHUB_TOKEN can never have.
New bot-token input (default ${{ github.token }}) is used for all
user-visible side effects. The PAT is now scoped to what actually needs
it: team membership, CODEOWNERS fetch, listFiles, pulls.get, and
workflow dispatch.
Side benefit: the PAT's required scopes shrink. It no longer needs
Pull requests:write or Issues:write — just Contents:read + Members:read
(+ Actions:write for /stageblog). README updated to reflect this.
listFiles pagination + workflow dispatch can take a few seconds. Without
an early ack, the commenter gets no feedback until the outcome reaction
lands. 👀 confirms the command was recognized and authorized while the
heavier work runs.
Extends slash-commands with a new /stageblog command that lets authorized
maintainers trigger a workflow_dispatch for staging blog previews from fork
PRs, where the normal pull_request-triggered preview cannot access secrets.
- Captures pr.head.sha at comment time and passes it to the dispatched
workflow so fork authors cannot push new commits between review and build
- Disabled unless stageblog-workflow input is set
- Refuses with stageblog-not-blog result if no changed file matches
stageblog-paths (default blog/**)
- Documents companion workflow pattern, additional PAT scope (Actions:
write), and SHA-pinning safety requirement
Move the handler logic out of the YAML script block into a standalone
main.js loaded via require(GITHUB_ACTION_PATH). This improves
readability and makes the JS easier to lint and unit-test directly.