Playbook: Vercel production deploy blocked — commit author lacks contributing access
See also:
git-identity-and-roles.md— the canonical identity × role × action matrix; §8 covers the Vercel commit-author seat requirement and the token-Action fix.
Trigger: A gh pr merge into a project’s main (or any Vercel-git-integration-tracked
branch) merges successfully on GitHub — checks green, issue auto-close via Closes #N works,
a GitHub Release publishes fine — but the actual Vercel deployment silently never ships. The
site keeps serving the previous build. There is no GitHub-side signal that anything failed;
gh pr view and gh pr checks show nothing wrong, because the failure happens entirely on
Vercel’s side of the GitHub integration webhook.
Confirmed live: 2026-08-18, radieu/et-operational-platform v2.10.2 release (PR #1704).
Also found one earlier occurrence in the same project’s deployment history
(commit e129a8bc, the #1629 GitHub App token migration) that never shipped until a later,
differently-authored commit carried the same code forward — so this is a recurring gap, not
a one-off.
How to confirm it
GitHub’s deployment status API is the fastest signal — check it after any main/production
merge, don’t just trust that the PR merged:
$REPO = "radieu/<project>"
$sha = git rev-parse origin/main
gh api "repos/$REPO/deployments?sha=$sha" --jq '.[] | "\(.id) \(.environment)"'
# Then, for the production-environment deployment id:
gh api "repos/$REPO/deployments/<id>/statuses" --jq '.[0] | "\(.state) \(.description)"'If you see failure / Deployment was blocked, confirm the exact cause via the Vercel API
(needs VERCEL_API_TOKEN from secrets/monitoring.env.sops):
VTOKEN=$(sops -d --input-type dotenv --output-type dotenv secrets/monitoring.env.sops | grep "^VERCEL_API_TOKEN=" | cut -d= -f2-)
curl -s "https://api.vercel.com/v6/deployments?projectId=<project>&limit=5&target=production" \
-H "Authorization: Bearer $VTOKEN" | grep -o '"errorMessage":"[^"]*"'The tell-tale error: "The Deployment was blocked because the commit author does not have contributing access to the project on Vercel." — paired with "seatBlock":{"blockCode": "TEAM_ACCESS_REQUIRED", ...} and a gitUser.login that isn’t a Vercel team member.
Root cause
Vercel’s GitHub integration gates auto-deploys on the pushing/merging commit’s GitHub
identity having a seat on the Vercel team with contributing access to that project — not on
whether the merge itself succeeded, and not on repo-level GitHub permissions. Any commit
(including a merge commit from gh pr merge) authored by a GitHub identity without that
Vercel-side seat gets silently blocked, with zero signal on the GitHub side.
This machine’s git identity for et-operational-platform work is ai-dev-windev-1 — a
dedicated bot account (mirroring the AI-Dev-BMS4-1 / ai-dev-bms4-2 naming pattern used for
the bms-4 workers).
Updated 2026-10-08 (#7592). Current state of the
devp24coms-projectsVercel team (Pro plan): the only paid/contributing member isdev.pinbox24@gmail.com(OWNER).ai-dev-windev-1was switched toVIEWER_FOR_PLUS(a free viewer, not a contributing seat), so its merge commits are still blocked by the git-integration author check — same as the bms-4 bot identities AI-Dev-BMS4-1 / ai-dev-bms4-2 and the App-2109526 PR-merge bot, none of which have a seat either. Theradieu@gmail.cominvite is unconfirmed and does not consume a seat (verified in the dashboard — so there is no seat human-action to chase). Vercel Pro has no custom roles, so “give each bot its own scoped role” is not an available fix — a contributing seat is all-or-nothing. That is why the durable fix below moves off the git-integration path entirely rather than chasing per-identity seats.
Immediate fix — redeploy via the Vercel API directly
A direct Vercel API deployment request is not gated by the commit-author check (that check is specific to the GitHub-webhook integration path) — the API token’s own permissions are what’s checked instead. This is the fast, safe workaround; it does not touch team membership/permissions.
export SOPS_AGE_KEY_FILE="C:\Users\konar\.age\p24-infra-keys.txt"
VTOKEN=$(sops -d --input-type dotenv --output-type dotenv secrets/monitoring.env.sops | grep "^VERCEL_API_TOKEN=" | cut -d= -f2-)
unset SOPS_AGE_KEY_FILE
MAINSHA=$(git rev-parse origin/main)
cat > ./_deploy_payload.json <<EOF
{
"name": "<vercel-project-name>",
"project": "<vercel-project-id>",
"target": "production",
"gitSource": {
"type": "github",
"org": "radieu",
"repo": "<repo-name>",
"ref": "main",
"sha": "$MAINSHA"
}
}
EOF
curl -s -X POST "https://api.vercel.com/v13/deployments" \
-H "Authorization: Bearer $VTOKEN" -H "Content-Type: application/json" \
--data-binary @./_deploy_payload.json
VTOKEN=""
rm -f ./_deploy_payload.jsonPoll GET /v13/deployments/<id> for readyState until READY, then confirm the alias
field includes the real production domain(s) — not just the internal *.vercel.app URL.
Verify live with curl -s -o /dev/null -w "HTTP %{http_code}\n" https://<prod-domain>.
Durable fix — deploy from a GH Action, not the git integration (#7592, target flow)
Because Vercel Pro has no custom roles and new bot identities keep appearing, chasing per-identity team seats is a losing game. The target fix makes production deploys independent of the commit author by deploying from a GitHub Action that calls the Vercel CLI/API with a token — the token path is not subject to the commit-author seat check.
The reusable building block lives in this repo: .github/workflows/reusable-deploy-vercel.yml
(workflow_call). It deploys via the token, polls readyState until READY, optionally verifies
the production alias with HTTP 200, and on any failure fires the Error-Notification Standard
(Discord embed + GH bug issue). Consumer repos call it in one line.
Per production project — roll out one at a time. In-scope repos (actively maintained, a live git link in Vercel, deploy activity ≤ 2 months as of 2026-10-08):
| Repo | Vercel project(s) | Notes |
|---|---|---|
radieu/et-operational-platform | et-operational-platform (main), et-operational-platform-7ktl (staging) | start here |
radieu/whatsup-android-chat-puller | whatsup-android-chat-puller, p24-wa-a | two projects, same repo |
radieu/radekkonarski-personal-brand | radekkonarski-com |
Out of scope: brandpilot, portal, wasabi-iam-rotator already deploy outside the git
integration (no git link in Vercel), so they are not affected by this bug. Art-Agency (last deploy
2026-06-28) is a later decision. portal/ additionally lives in p24-infra and deploys via its own
portal-deploy.yml (which also builds the MkDocs docs), so it is intentionally left on that workflow.
Each in-scope repo needs its own issue/PR — a p24-infra PR cannot add workflow files to another repo.
-
Add a push-to-
mainAction in the project repo that calls the reusable workflow here:# .github/workflows/deploy-vercel.yml in the project repo on: push: branches: [main] workflow_dispatch: jobs: deploy: uses: radieu/p24-infra/.github/workflows/reusable-deploy-vercel.yml@main with: environment: production vercel_project_name: <project> production_url: https://<prod-domain> # optional HTTP-200 gate secrets: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} DISCORD_ERRORS_WEBHOOK: ${{ secrets.P24_DISCORD_INFRA_SCRIPTS_ERRORS_WEBHOOK_URL }}VERCEL_ORG_ID/VERCEL_PROJECT_IDpin the deploy to the right team+project without a committed.vercellink (the reusable job exports them as env for the CLI). A repo with two Vercel projects (e.g. whatsup-android-chat-puller) calls the reusable workflow once per project with the matchingVERCEL_PROJECT_ID. -
Only after that project’s first green Action deploy, turn off the git auto-deploy so the two paths don’t double-ship. Set
git.deploymentEnabled.main = falseon the project via the Vercel API (do NOT disable it before the Action is proven, or the project ships nothing):# VERCEL_API_TOKEN from secrets/monitoring.env.sops — secret-manager / operational step curl -s -X PATCH "https://api.vercel.com/v9/projects/<project-id>" \ -H "Authorization: Bearer $VTOKEN" -H "Content-Type: application/json" \ -d '{"git":{"deploymentEnabled":{"main":false}}}'
Token: the deploy token is only ever a GH Secret / SOPS value delivered through the existing
secret-manager flow — never printed, never hardcoded. VERCEL_TOKEN already exists as a GH Secret
in p24-infra (used by portal-deploy.yml), so the reusable workflow itself needs no new secret; a
consumer repo that lacks VERCEL_TOKEN / VERCEL_ORG_ID / VERCEL_PROJECT_ID files a
secret-manager request to add them as repo GH Secrets.
The CLI still inherits the checkout’s git author — strip .git first (#7706)
The token path removes the webhook commit-author check, but it is not enough on its own. The
vercel deploy CLI reads git metadata from the local checkout — the HEAD commit author — and
attaches it to the deployment as githubCommitAuthor*. Vercel then re-applies the same
commit-author seat check to that metadata, so a bot-authored tip commit is still BLOCKED, and the
CLI (which waits for the deployment to become READY) hangs on the blocked deployment instead of
exiting.
Confirmed 2026-10-09 (#7706): radieu/radekkonarski-personal-brand run 37901972898
(deploy-vercel.yml → this reusable workflow), head commit e7903f2f authored by ai-dev-bms4-2:
the CLI deployment for radekkonarski-com ended readyState=BLOCKED,
readyStateReason="The deployment was blocked because the commit author doesn't have permission to create deployments for this project.", githubCommitAuthorName=ai-dev-bms4-2. The “Deploy” step
then hung ~55 minutes until cancelled by hand. The same repo’s et-operational-platform deploys only
succeeded because their tip commits were authored by radieu (who does hold a seat) — a
bot-authored tip there would block identically.
Fix (in reusable-deploy-vercel.yml): rm -rf .git at the workspace root before
vercel deploy, so the CLI has no commit author to report. This is genuine author-independence —
no author, not a borrowed/forged one (no --reset-author, no --meta githubCommitAuthor*
impersonation). VERCEL_ORG_ID / VERCEL_PROJECT_ID (exported as env) still pin the team+project
without the .git link, so the deploy targets the right project.
Hard timeout: the deploy step carries timeout-minutes (input deploy_timeout_minutes, default
15) and wraps vercel deploy in timeout ~30s under that backstop, so a blocked/queued deployment
fails fast with the Error-Notification Standard (Discord + GH bug issue) instead of hanging an
hour. The failure notifier runs vercel inspect on the captured deployment URL and includes the
deployment’s readyStateReason in the Discord/issue message when available.
Diagnose a hang/block: check readyStateReason on the deployment — if it names the commit author
("commit author doesn't have permission" / TEAM_ACCESS_REQUIRED), the checkout still carried a
.git with a seatless author. Confirm via the Vercel API (see How to confirm it above) or
vercel inspect <deployment-url> --token "$VTOKEN".
Affected callers (all use @main of this reusable workflow, so the fix applies as soon as it
merges): radieu/et-operational-platform (vercel-deploy.yml),
radieu/radekkonarski-personal-brand (deploy-vercel.yml), radieu/whatsup-android-chat-puller
(PR #227). After merge, re-run radekkonarski-personal-brand’s deploy-vercel.yml
(workflow_dispatch) to verify a bot-authored tip commit now deploys without blocking.
Prevention
After every rc → main (or equivalent production) merge, verify the deployment actually
shipped — do not trust a green gh pr merge alone. Add the confirmation check above as a
standard step in /rc-to-main-style promotion flows for any project using Vercel’s GitHub
integration, until the durable fix lands.