{"record":{"id":"3008e8533ada0bdf","repo":"decolua/9router","slug":"deployment-failed-data-readystate","errorCode":null,"errorMessage":"Deployment failed: ${data.readyState}","messagePattern":"Deployment failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app/api/proxy-pools/vercel-deploy/route.js","lineNumber":51,"sourceCode":"  });\n\n  return new Response(response.body, {\n    status: response.status,\n    headers: response.headers,\n  });\n}\n`;\n\nasync function pollDeployment(deploymentId, token, maxMs = 120000) {\n  const start = Date.now();\n  while (Date.now() - start < maxMs) {\n    const res = await fetch(`${VERCEL_API}/v13/deployments/${deploymentId}`, {\n      headers: { Authorization: `Bearer ${token}` },\n    });\n    const data = await res.json();\n    if (data.readyState === \"READY\") return data;\n    if (data.readyState === \"ERROR\" || data.readyState === \"CANCELED\") {\n      throw new Error(`Deployment failed: ${data.readyState}`);\n    }\n    await new Promise((r) => setTimeout(r, 3000));\n  }\n  throw new Error(\"Deployment timed out\");\n}\n\n// POST /api/proxy-pools/vercel-deploy\nexport async function POST(request) {\n  try {\n    const body = await request.json();\n    const vercelToken = body.vercelToken;\n    const projectName = body.projectName?.trim() || `relay-${Date.now().toString(36)}`;\n\n    if (!vercelToken) {\n      return NextResponse.json({ error: \"Vercel API token is required\" }, { status: 400 });\n    }\n\n    // Deploy relay function to Vercel","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/api/proxy-pools/vercel-deploy/route.js#L33-L69","documentation":"pollDeployment polls the Vercel v13 deployments API every 3 seconds waiting for readyState READY. When Vercel reports the deployment's readyState as ERROR or CANCELED, the function throws `Deployment failed: <state>` to abort the wait instead of returning a broken deployment. It means Vercel itself finished the build with a failure or someone/something canceled it.","triggerScenarios":"The POST /api/proxy-pools/vercel-deploy create-deployment call succeeds (202) but the resulting relay deployment later transitions to readyState ERROR (build/runtime failure of the generated api/relay.js edge function, invalid projectSettings, region/plan restrictions) or CANCELED (superseded by a newer deployment, manual cancel, or account-level cancellation).","commonSituations":"Edge runtime code rejected by Vercel (unsupported API in the bundled relay handler), a Vercel project with build checks/protected deployment policies, deploying on a plan that disallows the requested configuration, or a subsequent deploy of the same project canceling this one.","solutions":["Check the deployment details via GET /v13/deployments/<id> (or the Vercel dashboard URL from the response) and inspect the build logs to see the actual build/runtime error.","Retry the deploy once the underlying issue is fixed; the error is deterministic for a given payload, so fixing the deployment payload (files, projectSettings, target) is required, not a retry loop.","Verify the Vercel token's team/scope matches the project and that the plan supports edge functions and the requested settings.","If deployments are being CANCELED by concurrent deploys, serialize deployments (deploy one project at a time) or use unique project names."],"exampleFix":"// before\nif (data.readyState === \"ERROR\" || data.readyState === \"CANCELED\") {\n  throw new Error(`Deployment failed: ${data.readyState}`);\n}\n// after\nif (data.readyState === \"ERROR\" || data.readyState === \"CANCELED\") {\n  const logsRes = await fetch(`${VERCEL_API}/v2/deployments/${deploymentId}/events?limit=20&builds=1`, { headers: { Authorization: `Bearer ${token}` } });\n  const events = await logsRes.json().catch(() => []);\n  const last = events.filter((e) => e.text).map((e) => e.text).slice(-5).join(\"\\n\");\n  throw new Error(`Deployment failed: ${data.readyState}${last ? `\\n${last}` : \"\"}`);\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check token scope/validity before deploying\nconst res = await fetch(\"https://api.vercel.com/v2/user\", { headers: { Authorization: `Bearer ${token}` } });\nif (!res.ok) throw new Error(\"Invalid Vercel token — fix before deploying\");","typeGuard":"function isFailedDeployment(d) {\n  return d && (d.readyState === \"ERROR\" || d.readyState === \"CANCELED\");\n}","tryCatchPattern":"try {\n  const ready = await deployAndPoll(body);\n} catch (err) {\n  if (/^Deployment failed: (ERROR|CANCELED)$/.test(err.message)) {\n    // surface Vercel dashboard/logs link to the user; do not retry blindly\n  } else {\n    throw err;\n  }\n}","preventionTips":["Fetch and show deployment build logs (GET /v13/deployments/<id>/events) instead of only the state string","Test the relay payload on a throwaway project before production deploys","Serialize deployments per project to avoid CANCELED-by-supersede","Validate the Vercel token and plan capabilities before creating the deployment"],"tags":["vercel","deployment","http-api"],"backgroundTag":"deployment-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}