En un deploy, la identidad que importa es la del autor del commit
En Vercel, el permiso para publicar viaja con el commit, no con quien corre el comando. Cómo se ve un deploy BLOCKED por asiento, por qué la CLI lo muestra como UNKNOWN, dónde está la causa en la API y las dos salidas que hay.
También en: English
Cuando piensas en quién tiene permiso para publicar un sitio, lo natural es
pensar en la persona que aprieta el botón: la cuenta que corre vercel --prod
o la integración que reacciona al push. En Vercel hay otra identidad que pesa
tanto o más, y es la que menos se mira: el autor del commit.
Lo aprendimos en vendear, nuestro sitio de
vidrieras con vendedor con IA. El proyecto de Vercel está en un equipo del plan
Hobby con un solo miembro, la cuenta dueña. Un colaborador que no es miembro de
ese equipo firmó dos commits en main, y los dos deployments que dispararon
quedaron en estado BLOCKED. El código estaba bien, la cuenta estaba activa y
el deploy lo intentaba la cuenta dueña. Lo que no tenía asiento era el autor.
Cómo funciona el chequeo
Antes de construir, Vercel compara la identidad de git del commit con los
miembros del equipo. Si el autor no está, el deployment no llega a construirse:
queda BLOCKED y la causa se guarda en el propio deployment:
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v13/deployments/<id>?teamId=<team>" \
| jq '{readyState, readyStateReason, seatBlock}'
En nuestro caso, seatBlock.blockCode era TEAM_ACCESS_REQUIRED.
Esto explica por qué el sitio llevaba semanas publicándose solo con cada push a
main sin problemas: todos esos commits los había firmado la cuenta dueña. El
chequeo no es nuevo ni intermitente. Simplemente no tiene nada que decir hasta
que firma alguien de afuera.
Lo que muestra cada herramienta
Este es el detalle que hace caro el problema. El estado real existe, pero no todas las herramientas lo muestran:
| Herramienta | Qué muestra |
|---|---|
vercel ls |
UNKNOWN, que se lee como "viejo", no como "falló" |
vercel inspect <url> |
status UNKNOWN, sin una línea de causa |
GET /v13/deployments/<id> |
BLOCKED, con readyStateReason y seatBlock |
| El dominio | 200, sirviendo el deploy anterior |
El sitio respondía bien, el repo estaba al día y la CLI no marcaba ningún error. Lo único distinto era que producción servía un commit de tres días antes, y eso sólo se ve si le preguntas al sitio qué versión sirve (para eso tenemos un endpoint de versión).
Lo que no lo destraba
Tres caminos parecen razonables y ninguno sirve, porque los tres conservan al mismo autor:
- Esperar. Un
BLOCKEDse parece a un tope de uso del plan, algo que se resuelve solo. No lo es: un deploy nuevo lanzado días después quedó bloqueado en el mismo segundo, con la cuenta activa y sin problemas de facturación. Si lo que parece un cupo no se mueve con el tiempo, mira el permiso. - Publicar a mano desde el disco. El deployment lo crea la cuenta dueña,
pero la CLI adjunta los metadatos de git de la carpeta local, incluido el
autor del último commit (
githubCommitAuthorName), y el chequeo corre sobre ellos. vercel redeploy. Un deployment bloqueado responde400: no se puede redesplegar y pide un commit nuevo.
Las dos salidas
Darle asiento al autor. Es lo correcto si esa persona va a firmar commits seguido. En el plan Hobby no se pueden sumar miembros, así que implica pasar el equipo a Pro.
Que el commit que llega a la rama de producción lo firme alguien con asiento. Para destrabar el caso alcanzó un commit vacío en
main, firmado por la cuenta dueña:git commit --allow-empty -m "deploy: republicar main" git push origin mainLa integración construyó el mismo árbol y el deployment quedó
READY. A futuro, la misma regla aplica a los merges: si los cierra la cuenta dueña con squash o rebase, el commit que llega amainlleva su autoría.
La segunda salida destraba hoy, pero no cambia el mecanismo: el siguiente
commit de alguien sin asiento que quede arriba en main va a frenar igual.
La idea que conviene llevarse
En un pipeline solemos pensar el permiso como algo de la sesión: quién está logueado, qué token usa el CI. Acá el permiso viaja con el código. El mismo árbol, desplegado por la misma cuenta, se publica o no según el nombre que quedó escrito en el commit.
Por eso, cuando un deploy se frena sin un error claro, la primera pregunta no es "¿quién lo lanzó?", sino "¿quién firmó lo que está arriba en la rama?". Y la segunda, que lo detecta antes de que te lo diga un cliente, es "¿qué commit está sirviendo producción?":
curl -s https://www.vendear.com/api/version