DeployKit

Recover a failed publish

Distinguish a rejected request, a lost response and a deployment that already exists.

Save a unique application-owned publish attempt before sending deployment creation. For Vercel, pass its stable ID as operationId and save the returned deployment receipt immediately. An operation ID correlates recovery. It is not an idempotency key or an exactly-once guarantee.

Choose the next action from the failure

EvidenceWhat to do
Validation, source or integrity failureRepair the input before publishing it again
Read/poll failure with a saved deployment IDRetry observation of that existing ID
Deployment build failedInspect its reason and provider logs; keep the failed receipt
Create response lost, timeout or cancellation during creationReconcile the original operation ID
Application verification failedRetry or fix your checker against the saved deployment
Activation response lostObserve activation state; do not recreate the deployment

Transport retries are bounded. Reads and content-addressed uploads can retry; create and activation writes are not automatically repeated. A caller should never wrap the whole publish workflow in an unconditional retry.

Reconcile with Vercel

import { ProviderError } from "@deploykit/node"
import { createClient } from "@deploykit/node/vercel"
import type { VercelOptions } from "@deploykit/node/vercel"

export async function recoverPublish(
  config: VercelOptions,
  input: { appId: string; operationId: string; signal: AbortSignal }
) {
  const client = createClient(config)
  try {
    const outcome = await client.reconcileDeployment(input.appId, input.operationId, {
      signal: input.signal
    })
    switch (outcome._tag) {
      case "Recovered":
        return { state: "created" as const, deployment: outcome.deployment }
      case "Unknown":
        return {
          state: "needs-review" as const,
          reason: outcome.reason,
          candidates: outcome.candidates
        }
      default: {
        const exhaustive: never = outcome
        return exhaustive
      }
    }
  } catch (error) {
    if (error instanceof ProviderError) {
      console.error({ operation: error.operation, code: error.code, outcome: error.outcome })
    }
    throw error
  } finally {
    await client.close()
  }
}

For Recovered, persist the receipt and continue observing that deployment. For Unknown, preserve the attempt for review. An empty candidates array does not prove that creation never happened. Search windows, timing and provider metadata can prevent a unique result.

Cloudflare Pages does not implement this reconciliation capability. Inspect the provider before deciding whether to start a new production publish.

Keep errors useful

ProviderError can include operation, statusCode, code, requestId, retryAfterMs, outcome and recovery. Fields are optional; inspect what is present instead of assuming all network failures have an HTTP status.

Use Provider.encodeFailure / decodeFailure from @deploykit/core/provider in Effect code to carry supported failures across JSON boundaries. SDK defects, filesystem errors and application errors are not all members of that wire union. Do not serialize credentials, signed URLs or arbitrary raw provider response bodies.

Errors explains the full taxonomy; API declarations show the exact schemas.

Last updated on

On this page