Skip to content

[Bug]: Cloudflare Pages deploys are always production — target: 'preview' is ignored, no way to publish a gated preview #4483

@jxstanford

Description

@jxstanford

Before you submit

  • I searched existing issues and confirmed this is not a duplicate.
  • I replaced the example text with the real behavior, reproduction steps, expected result, and version from the app's About menu or od --version.

What happened?

The Cloudflare Pages deploy provider always creates a production deployment served at the public root <project>.pages.dev, even though both DeployConfigResponse.target and DeploymentInfo.target are typed as 'preview'. There is no config or API option to request a non-production (preview) deployment, so a generated artifact is publicly live the instant it's deployed — you can't stage a gated preview URL for review before it goes public.

Steps to reproduce

  1. Configure the Cloudflare Pages deploy provider (token + account ID).
  2. Deploy any project artifact (POST /api/projects/<id>/deploy with providerId: "cloudflare-pages").
  3. Note the returned url is the production root <project>.pages.dev and is publicly reachable immediately.
  4. In the Cloudflare dashboard, open the deployment → Environment = Production (never Preview).
  5. There is no setting or request field to make this a preview deployment.

Expected behavior

Because the deploy config and every DeploymentInfo are declared target: 'preview', a deploy should be able to produce a Cloudflare Pages preview deployment — a <hash>.<project>.pages.dev URL (deploy branch ≠ production_branch) that can be Access-gated, leaving the production hostname untouched. At minimum, OD should expose an explicit choice between production and preview rather than silently forcing production.

Open Design version

0.10.2 (od --version; reproduced on main @ 2ff2d79)

Platform

macOS (Apple Silicon) — but the defect is in the daemon deploy code and is platform-independent.

Additional context

Root cause (traced to source):

  • apps/daemon/src/deploy.ts:498 hardcodes form.append('branch', 'main') on the deployment FormData.
  • apps/daemon/src/deploy.ts:1000 sets production_branch: 'main'. Cloudflare Pages treats branch === production_branch as Production, so every deploy is Production.
  • target: 'preview' (deploy.ts:168/182/432/543, routes/deploy.ts:125) and the contract literals (packages/contracts/src/api/projects.ts:506,527) are never translated into the Cloudflare deploy branch — the label only feeds internal metadata and DNS (pagesTarget, deploy.ts:628, is derived from the pages.dev hostname, not the branch).
  • routes/deploy.ts accepts no branch/target override, so no caller can request a non-production deploy.

Suggested minimal direction: derive the deploy branch from an explicit deploy mode (preview → a non-main branch such as preview; publish/approve → main) and thread an optional target through routes/deploy.ts. This also unblocks #2357 (versioned handoff publishing), which presumes a working preview path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions