Commit graph

44 commits

Author SHA1 Message Date
Den Delimarsky
0540b7f48f
Merge pull request #16 from modelcontextprotocol/den/lgtm-codeowner-bypass
slash-commands: direct-merge fallback for code-owner review
2026-03-27 18:31:57 -07:00
Den Delimarsky
cf4e6c7382
slash-commands: fall back to direct merge when bot approval fails code-owner check
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
2026-03-28 01:31:29 +00:00
Den Delimarsky
8cde341728
Merge pull request #15 from modelcontextprotocol/den/lgtm-status-direct-write
slash-commands: collapse github-token and bot-token into one input
2026-03-26 16:36:14 -07:00
Den Delimarsky
e518d5be36
slash-commands: collapse github-token and bot-token into one input
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
2026-03-26 23:31:15 +00:00
Den Delimarsky
71ea84b9b0
Merge pull request #14 from modelcontextprotocol/den/lgtm-status-direct-write
slash-commands: core-only /lgtm, auto-merge, GitHub App flow
2026-03-26 16:28:46 -07:00
Den Delimarsky
63db292169
slash-commands: remove commit-status gate, document GitHub App flow
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.
2026-03-26 23:21:16 +00:00
Den Delimarsky
b76984fd53
slash-commands: fix README github-token description drift 2026-03-26 22:55:32 +00:00
Den Delimarsky
96c6283aa5
slash-commands: restrict /lgtm to core maintainers, add auto-merge
/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).
2026-03-26 22:51:03 +00:00
Den Delimarsky
9105fff368
Merge pull request #13 from modelcontextprotocol/den/lgtm-status-direct-write
slash-commands: direct commit-status writes + /lgtm force
2026-03-23 20:10:29 -07:00
Den Delimarsky
c6938c24e6
slash-commands: add /lgtm force for core/lead maintainers
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.
2026-03-24 03:07:04 +00:00
Den Delimarsky
a8f72c0c90
Merge pull request #12 from modelcontextprotocol/den/lgtm-status-direct-write
slash-commands: write commit status directly from /lgtm handler
2026-03-12 13:17:45 +00:00
Den Delimarsky
332da5ae1a
slash-commands: write commit status directly from /lgtm handler
/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).
2026-03-12 13:15:43 +00:00
Den Delimarsky
f3ad04958f
Merge pull request #11 from modelcontextprotocol/den/repost-comment-mode
cloudflare-pages-preview: add comment-mode=repost (minimize old + post fresh)
2026-03-11 23:26:01 +00:00
Den Delimarsky
e6b64f4192
cloudflare-pages-preview: add comment-mode=repost, extract scripts
New comment-mode input:
- 'sticky' (default, backwards-compat): update the existing marker comment
  in place — same behavior as before
- 'repost': minimize all prior marker comments as OUTDATED (GitHub's native
  hide-comment UI via GraphQL minimizeComment mutation) and post a fresh
  comment at the bottom of the conversation

Useful for /stageblog where each staging should produce a visible new
comment while keeping history collapsible rather than lost.

Both deploy and cleanup accept the input and must match. cleanup in repost
mode minimizes all prior comments and posts a final cleanup note.

Also:
- Extract both comment scripts to sibling comment.js files (same pattern as
  slash-commands/main.js) — the inline YAML scripts were getting long
- Paginate listComments (was only fetching the first 30)
2026-03-11 23:24:51 +00:00
Den Delimarsky
8e0308e4dd
Merge pull request #10 from modelcontextprotocol/den/status-sync-race
slash-commands/status: force pending on synchronize (close auto-merge race)
2026-03-11 23:13:39 +00:00
Den Delimarsky
60ed112c52
slash-commands/status: force pending on synchronize to close auto-merge race
When new commits are pushed, the status job and handle job both fire on
the same synchronize event and run in parallel. The event payload's
pr.labels reflect the PRE-push state — so if the PR was approved before
the push, the status job reads 'approved' from the payload and sets
success on the NEW SHA, even though handle is about to (or already did)
remove the label.

Observed in modelcontextprotocol#2375: label removed at 23:06:29, status
set success at 23:06:30 (one second later, from stale payload), merged
at 23:06:34.

Fix: add invalidate-on-push input (default true, mirroring the main
action). On synchronize with invalidation enabled, set pending
unconditionally — don't trust the payload labels. The subsequent
unlabeled event re-evaluates and stays pending.
2026-03-11 23:09:07 +00:00
Den Delimarsky
ae45899358
Merge pull request #9 from modelcontextprotocol/den/pending-status
Add slash-commands/status: pending commit status instead of red ❌
2026-03-11 22:58:26 +00:00
Den Delimarsky
f931dcd1bf
Add slash-commands/status sub-action for pending-state merge gate
The previous status-job pattern (inline shell that exits 1 when 'approved'
is missing) shows a red failed check on every fresh PR. GitHub Actions jobs
only have pass/fail — there's no way to express 'pending' via exit code.

The new sub-action uses the commit-status API to set state=pending until
the approved label appears. Hold label produces state=failure. The status
job itself now always succeeds; the commit status it creates is what branch
protection enforces.

Caller workflow swaps the 20-line jq+exit block for a one-line uses: call,
adds statuses:write to permissions, and marks 'slash-commands/approval'
as a required status check (replacing the job-based check).
2026-03-11 22:56:18 +00:00
Den Delimarsky
60e40e0d12
Merge pull request #8 from modelcontextprotocol/den/fix-module-not-found
slash-commands: use raw fetch for team check (fix MODULE_NOT_FOUND)
2026-03-11 22:53:00 +00:00
Den Delimarsky
5cfe4d9921
Use raw fetch for team membership check — bypass Octokit auth entirely
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.
2026-03-11 22:52:06 +00:00
Den Delimarsky
bd238ed6e4
Merge pull request #7 from modelcontextprotocol/den/fix-auth-override
slash-commands: fix PAT auth override silently failing on team checks
2026-03-11 22:49:51 +00:00
Den Delimarsky
5d105acf64
Construct orgClient inside github-script to fix module resolution
@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.
2026-03-11 22:49:13 +00:00
Den Delimarsky
924ee55f9b
Merge pull request #6 from modelcontextprotocol/den/fix-bot-token
Fix: require('@actions/github') MODULE_NOT_FOUND (production hotfix)
2026-03-11 20:53:04 +00:00
Den Delimarsky
9fc2acf515
Fix: require('@actions/github') not resolvable from external main.js
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.
2026-03-11 20:52:21 +00:00
Den Delimarsky
658d01c6d7
Merge pull request #5 from modelcontextprotocol/den/stageblog-command
slash-commands: early ack reaction + post as github-actions[bot]
2026-03-11 20:49:21 +00:00
Den Delimarsky
cad4102f07
Post comments/reactions/labels/reviews as github-actions[bot]
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.
2026-03-11 20:43:06 +00:00
Den Delimarsky
df23d396f5
Add early 👀 ack reaction for /stageblog
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.
2026-03-11 20:43:06 +00:00
Den Delimarsky
e2fbc602d7
Merge pull request #4 from modelcontextprotocol/den/stageblog-command
Add hugo-build composite action
2026-03-11 20:37:29 +00:00
Den Delimarsky
2451a73720
Add hugo-build composite action
Installs Go + Hugo extended and builds a Hugo site in preview
(--buildDrafts --buildFuture, development env) or production mode.

Extracted from the MCP repo's blog-preview, deploy-blog, and stage-blog
workflows where the ~12-line Go+Hugo install+build block was duplicated.
Placing it here (rather than as a local .github/actions/ composite in the
MCP repo) means stage-blog.yml can reference it at @main — fork-proof by
construction, no two-checkout dance needed when building fork-submitted
content.
2026-03-11 20:36:43 +00:00
Den Delimarsky
df3720fc27
Merge pull request #3 from modelcontextprotocol/den/stageblog-command
Add pr-number input to cloudflare-pages-preview for workflow_dispatch
2026-03-11 20:26:49 +00:00
Den Delimarsky
a4bb78216a
Add pr-number input to cloudflare-pages-preview actions
Both deploy and cleanup previously read context.payload.pull_request.number
directly, which throws on workflow_dispatch and other non-PR triggers.

New optional inputs:
- deploy: pr-number (default: event PR number), commit-sha (default: github.sha)
- cleanup: pr-number (default: event PR number)

If pr-number resolves to empty/0, the comment step is skipped gracefully
instead of crashing.

This lets /stageblog's companion workflow_dispatch receiver call the
composite deploy action directly instead of inlining wrangler + noindex
injection. The noindex logic staying in the actions repo (rather than in
a script file inside the caller repo that gets checked out at the fork's
SHA) is also the secure path — fork authors can't tamper with it.

The slash-commands README example is updated to use the deploy action
with the new inputs. The earlier commit on this branch (which rewrote
the example to call wrangler directly) is effectively superseded.
2026-03-11 20:25:04 +00:00
Den Delimarsky
38a01a9736
Fix README example: use wrangler directly, not deploy action
The cloudflare-pages-preview/deploy action reads context.payload.pull_request.number
unconditionally in its comment step, which throws on workflow_dispatch. The
previous example would crash. Updated to call wrangler-action directly and
post the comment manually.
2026-03-11 20:23:25 +00:00
Den Delimarsky
9fccbf5d69
Merge pull request #2 from modelcontextprotocol/den/stageblog-command
Add /stageblog slash command for fork-PR blog preview dispatch
2026-03-11 20:13:31 +00:00
Den Delimarsky
5bc398b196
Add /stageblog slash command to dispatch blog preview workflow
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
2026-03-11 20:12:52 +00:00
Den Delimarsky
5c138acba1
Merge pull request #1 from modelcontextprotocol/slash-commands
Add slash-commands composite action for /lgtm and /hold
2026-03-11 16:48:52 +00:00
Den Delimarsky
0af3ec7975
Extract slash-commands script to main.js
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.
2026-03-11 16:45:10 +00:00
Den Delimarsky
d5df36515c
Add slash-commands composite action for /lgtm and /hold
Prow-style PR approval via comment commands, gated by org team membership
or CODEOWNERS of the PR's changed files.

- /lgtm adds 'approved' label + bot APPROVE review; /lgtm cancel undoes both
- /hold adds 'do-not-merge/hold'; /hold cancel or /unhold removes it
- Self-approval is blocked; unauthorized attempts get a thumbs-down reaction only
- New commits pushed to a PR invalidate approval (label removed, review dismissed)
- CODEOWNERS is fetched from the PR base ref to prevent tampering

Requires a PAT or App token with read:org — default GITHUB_TOKEN cannot
check team membership.

Closes modelcontextprotocol/modelcontextprotocol#2199
2026-03-11 16:40:07 +00:00
Den Delimarsky
48110cb715
Simplify secrets table: just the action input name, not full YAML
The caller already knows how to wire secrets.X into with: inputs; the table
only needs to name the input, not repeat the secret name in a YAML snippet.
2026-03-10 16:49:12 +00:00
Den Delimarsky
2a230c7793
Use concrete secret names in README table and usage example
- cloudflare-pages-preview/README.md: secrets table now prescribes exact repo
  secret names (CF_PAGES_PREVIEW_API_TOKEN, CF_PAGES_PREVIEW_ACCOUNT_ID) with
  the literal passed-as YAML snippet. Usage example updated to match.
- CLAUDE.md: mandate the three-column table format with concrete names, and
  require usage examples to reference the same names
2026-03-10 16:47:40 +00:00
Den Delimarsky
05549c0320
Expand secrets docs: where to get values and how to set them
- cloudflare-pages-preview/README.md: replace secrets table with per-secret
  subsections giving exact CF dashboard navigation for obtaining the API token
  (My Profile > API Tokens > Custom Token, Account > Pages > Edit permission)
  and account ID (right sidebar on any domain/Workers overview), plus explicit
  guidance to store each as a named repo secret
- CLAUDE.md: README secrets section must now document the exact UI path to
  obtain each secret, minimum permission scope, input mapping, and storage
  location
2026-03-10 16:45:57 +00:00
Den Delimarsky
456bb1d1f6
Document required secrets in action READMEs
- cloudflare-pages-preview/README.md: add dedicated Required secrets section
  listing the CF API token (with minimum permission scope) and account ID,
  which inputs they map to, and a note that github-token needs no extra secret
- CLAUDE.md: make Required secrets a mandatory README section for new actions
2026-03-10 16:44:08 +00:00
Den Delimarsky
9aa111f5e2
Convert README actions list to table, add CLAUDE.md
- README.md: list actions in a table with separate rows for deploy/cleanup
- CLAUDE.md: conventions for adding new composite actions (layout, shell
  requirement, env-var input passing, secrets, github-token, README
  requirements, validation)
2026-03-10 16:40:37 +00:00
Den Delimarsky
d0955aa69d
Add cloudflare-pages-preview composite actions
Two composite actions extracted from modelcontextprotocol's blog-preview workflow:

- deploy: injects noindex headers/meta, deploys to CF Pages under a per-PR
  branch alias, posts/updates a sticky PR comment with preview URLs
- cleanup: deletes all CF Pages deployments for the PR branch alias on close,
  updates the sticky comment

Callers provide a pre-built static HTML directory; the actions handle
everything Cloudflare-specific.
2026-03-10 16:38:31 +00:00
Den Delimarsky
12edb490d8
Initial commit 2026-03-10 16:25:10 +00:00