Skip to content

fix(cloudflare): fix a race between void secret put and the first void deploy - #760

Merged
cpojer merged 3 commits into
voidzero-dev:mainfrom
magurotuna:fix/secret-placeholder-create-only
Nov 9, 2026
Merged

cpojer merged 3 commits into
voidzero-dev:mainfrom
magurotuna:fix/secret-placeholder-create-only

Conversation

@magurotuna

Copy link
Copy Markdown
Member

Fixes a race where void secret put during the first void deploy can take the new app offline.

What goes wrong

sequenceDiagram
  participant S as void secret put
  participant CF as Cloudflare
  participant D as void deploy (first)
  S->>CF: set secret
  D->>CF: deploy app
  CF-->>S: 10007 (no Worker yet, answered before the deploy)
  CF->>D: deploy success
  alt before this PR
    S->>CF: deploy empty placeholder
    Note over CF: app replaced by the placeholder
  else this PR
    S->>CF: create Worker if missing
    CF-->>S: 409 (already exists)
    S->>CF: set secret again
    Note over CF: app stays live, secret added
  end
Loading
  • The site goes down and workers.dev is turned off, but both void secret put and void deploy print success.
  • It needs a first deploy to finish within about two seconds of a first secret put (for example, CI deploying a new project while someone sets a secret). So it should be rare, but when this happens, it is silent and very mysterious.
  • I reproduced it on a real account.

Fix

  • The secret command creates the Worker with POST /workers/workers. Cloudflare answers 409 if the name is taken.
    • 200 (Created): it deploys the placeholder and the secrets in one request.
    • 409 (Conflict): it never deploys the placeholder and retries the secret command instead.

Additional Notes

  • If a secret put fails right after creating the Worker (for example, the network drops), the Worker is left with no code. Later secret put runs then stop with steps to fix it, instead of deploying the placeholder on their own. Doing it automatically is unsafe: Void cannot tell this Worker from one that another secret put created a moment ago, and deploying the placeholder there would overwrite that command's secret. A small TLA+ model found this case.
  • fix(cloudflare): stop on unsupported runtimes and keep deploy failures readable and safe #721 is related but different: it stops deploy recovery from rolling back to a placeholder.
  • Tested against a real account: the race, secret-then-deploy, and the empty-Worker cases.

AI disclosure: I used Claude to write code and this PR. I reviewed them.

…g Worker

When a secret command got 10007, Void deployed an empty placeholder
without checking again that the Worker was missing. A first `void deploy`
that finished in between was replaced by the placeholder.

Create the Worker with `POST /workers/workers` first. Cloudflare answers
409 when the name is taken. After a create, upload and deploy the
placeholder with its secrets in one request. After a 409, retry the
secret command and never deploy the placeholder; if the Worker still has
no deployment, fail with recovery steps.

Accept the new placeholder header in @void/deploy-cloudflare, and prefer
an API token over a Global API Key for direct API requests, as cf does.
@magurotuna magurotuna changed the title fix(cloudflare): do not deploy the secret placeholder over an existing Worker fix(cloudflare): fix a race between void secret put and the first void deploy Nov 9, 2026
@cpojer

cpojer commented Nov 9, 2026

Copy link
Copy Markdown
Member

fyi slop review:

P1: A second timing window still allows the placeholder to replace a deployed app. At line 1018, this sequence remains possible:

  1. The secret command successfully creates the Worker.
  2. A concurrent deploy publishes the real application.
  3. The secret command uploads its placeholder with deploy=true, replacing the application and reporting success.

@cursor

cursor Bot commented Nov 9, 2026

Copy link
Copy Markdown

How to use the Graphite Merge Queue

Add the label graphite-auto-merge to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

… helpers

Report the reasons Cloudflare gives when the Worker create or placeholder
deploy fails, as the cf path did, instead of only the HTTP status.

Move the operation-process test helpers to test/helpers and run every
secret scenario through one harness.
…r-create-only

# Conflicts:
#	packages/void/test/unit/cloudflare-operation-process.test.ts
@magurotuna

Copy link
Copy Markdown
Member Author

@cpojer Yes, that race window remains, but it is much smaller.

  • Before this PR, the deploy only had to publish between the 10007 and the placeholder deploy.
  • Now its upload and publish both have to complete between the create and the placeholder deploy, which is a shorter window like under a second.

Cloudflare has no "deploy only if nothing is deployed" option. Closing this window fully would require a much bigger redesign around void deploy, which I decided not to do in this PR.

@magurotuna
magurotuna marked this pull request as ready for review November 9, 2026 11:38

@cpojer cpojer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation!

@cpojer
cpojer merged commit ba0d593 into voidzero-dev:main Nov 9, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants