Fresh no-Git CLI and REST deployments classified as Production despite documented Preview defaults

I am seeing a reproducible deployment-target classification problem on a Vercel Hobby account. I am posting publicly to share the reproducible evidence and ask what CLI or REST request is supported for a guaranteed Preview deployment.

Expected behavior

Current Vercel documentation says:

For a fresh project with no Git repository or branch metadata, I therefore expected an omitted target to produce:

target=preview
VERCEL_ENV=preview
VERCEL_TARGET_ENV=preview

Actual behavior

The only deployment created in each of three isolated fresh projects was classified as Production:

Attempt Git source/metadata Request path Result
Original dedicated project No provider Git link; deployment API had gitSource=null Vercel CLI 52.0.0 with --target preview target=production
Disposable canary A None; detached directory outside Git Vercel CLI default deployment, target omitted target=production
Disposable canary B None Direct POST /v13/deployments with inline files; target, customEnvironmentSlugOrId, gitSource, Git metadata, aliases and environment fields absent target=production

The two disposable canaries used different fresh projects but byte-identical source files and equivalent project settings. Each canary project had:

  • exactly one deployment;
  • no Git link;
  • no environment variables;
  • no custom environments;
  • no integrations;
  • no custom domains;
  • no requested alias or promotion;
  • deployment protection retained.

Both canary deployment API read-backs returned:

target=production
readyState=ERROR
aliasAssigned=false
gitSource=null
meta={}

Each canary used a classification-only build script that emitted exactly one record containing three allowlisted values and then intentionally exited nonzero in every environment. Both records were:

{
  "VERCEL_ENV": "production",
  "VERCEL_TARGET_ENV": "production",
  "OIDC_ENVIRONMENT": "production"
}

VERCEL_ENV and VERCEL_TARGET_ENV are documented system variables, and the OIDC environment claim is documented as development, preview, or production:

The intentional nonzero exits left both canaries in readyState=ERROR; neither reached a usable runtime, and no deployment URL was probed. The canaries contained no application source and used no custom domain, customer data, or provider integration.

Historical first-deployment behavior is a hypothesis, not a confirmed cause

Two 2019 issues in Vercel’s public repository document then-current first-deployment behavior:

Issue #2756 was closed in favor of #2757. When #2757 was closed, its author commented, “This is implemented now.” Because the issue requested an explanatory message for the then-current behavior, that closure confirms the message work, not that a first-deployment upgrade remains active today. I have not found current documentation saying that a first deployment is upgraded to Production or that such an upgrade overrides the documented Preview/default request semantics.

All three observations occurred on a project’s first deployment, so the historical behavior is a plausible hypothesis. It is not a proven root cause, proof of current intentional behavior, or proof that the same implementation path handled these deployments. The two controlled no-Git canaries also weigh against Git metadata or a CLI-only payload path as the sole explanation, but they do not isolate an account, project-state, or backend cause.

CLI and API behavior observed

In the installed Vercel CLI 52.0.0 source, the deployment client normalizes a literal Preview target to an omitted request target before sending the deployment request:

if (deploymentOptions.target === "preview") {
  deploymentOptions.target = void 0;
}

The detached CLI run reported that the source directory was not a Git repository and that it could not obtain a last commit. Its deployment-creation response still contained target:"production".

The direct REST canary removed the CLI from the request path and produced the same classification with no Git fields.

The current create-deployment API prose also leaves an ambiguity:

  • target says omission yields Preview;
  • customEnvironmentSlugOrId says omission targets the environment inferred from the branch, either Production or Preview;
  • target lists omitted, staging, production, or a custom-environment identifier, but does not list a built-in literal preview request value.

Related public reports

These recent Community posts describe Preview requests or non-production branches being classified as Production. They are separate user reports, not proof of a shared cause:

Questions for Vercel

  1. Is the historical first-deployment upgrade to Production still active under any current conditions? If so, where is that exception documented?
  2. Does that behavior, when active, override CLI --target preview or REST target omission?
  3. Is literal REST target:"preview" supported even though the current API prose does not list it as a built-in request value?
  4. Is customEnvironmentSlugOrId relevant to the built-in Preview environment, or only to separately created custom environments?
  5. What exact supported CLI command or REST request guarantees built-in Preview for a fresh project’s first deployment?
  6. Can the CLI, Environments, and create-deployment API documentation be updated to describe any first-deployment exception that still exists?

I have preserved the inert failed deployments and sanitized request/read-back evidence. I can provide private resource identifiers to Vercel staff through a non-public channel if needed. I will not retry the real application until there is a provider-confirmed or independently verified request path that guarantees Preview.

This report shares evidence only. It does not authorize deployment, promotion, cleanup, domain changes, or testing against production or customer resources.

Hi Foundationfivepro 7852,

Your canary setup is useful because it removes Git metadata, aliases, domains, env vars, and app code from the equation.

I don’t think I’d treat the REST docs as fully unambiguous here. The target field says omission should be Preview, but the same endpoint also says the default environment can be inferred from branch, and there is no branch in your no-Git case. So your result may be hitting a first-deployment/default-classification path that is not clearly documented.

One extra test that would make the report even tighter: on a disposable project with no secrets or real app code, run a second deployment after the first inert failed one:

vercel deploy --target=preview --logs

and also try the REST request with the body explicitly containing:

{
  "target": "preview"
}

Even if the API docs don’t list preview clearly as a request value, the outcome is useful:

  • if the second deployment becomes Preview, that points toward a first-deployment exception

  • if it is still Production, that points toward a broader no-Git/project/account classification issue

  • if target: "preview" is rejected, that confirms the docs/API contract gap directly

  • if target: "preview" is accepted but still produces Production, that is the strongest repro

I would keep doing exactly what you’re doing: only inert canaries, no provider keys, no customer code, and no aliases/domains until there is a confirmed Preview path.