Skip to content

fix(cloudflare): serve wrangler secrets via single-shot FIFO#774

Merged
theoephraim merged 2 commits into
mainfrom
fix/cf-wrangler-fifo-single-shot
Jun 11, 2026
Merged

fix(cloudflare): serve wrangler secrets via single-shot FIFO#774
theoephraim merged 2 commits into
mainfrom
fix/cf-wrangler-fifo-single-shot

Conversation

@theoephraim

@theoephraim theoephraim commented Jun 11, 2026

Copy link
Copy Markdown
Member

Problem

varlock-wrangler deploy / versions upload intermittently fail in Linux CI with wrangler reporting:

The contents of "/tmp/varlock-secrets-..." is not valid.

Fixes #739.

Root cause

@varlock/cloudflare-integration feeds resolved env to wrangler through a temporary FIFO so secrets never touch disk. The FIFO server re-armed a new writer in a tight setImmediate loop, continuously re-writing the same JSON.

A FIFO is an unframed byte stream. Wrangler reads the file with fs.readFileSync (read-until-EOF), and EOF only occurs once no writer holds the pipe open. Because the loop re-opened a writer immediately after each close, a reader could read several concatenated copies of the JSON ({...}{...}{...}) before ever seeing EOF — which fails JSON.parse, falls back to dotenv, yields 0 keys, and surfaces as "is not valid".

It's intermittent and Linux-only because it depends on kernel pipe scheduling; macOS reliably gives a clean EOF between writes, so it never reproduces locally.

Diagnostic signature: the bad read's byte count is always an exact integer multiple of the real payload size (e.g. 721884 = 3 × 240628).

Fix

Writers now serve exactly one copy then exit (process.exit(0) instead of setImmediate(serve)), so closing the write fd delivers a clean EOF after one copy — no concatenation possible.

Once that copy is consumed (the writer exits cleanly), a fresh single-shot writer is re-armed so the next reader has content available. This is required because wrangler reads these files more than oncewrangler types re-reads the --env-file, and the deploy/upload path can re-read the --secrets-file — so a pure one-shot writer would leave the second read hanging. Re-arming is generation-guarded so only one writer is ever armed at a time, meaning copies can never overlap (no concatenation).

The FIFO is intentionally kept rather than replaced with a regular temp file: resolved secrets never exist as a file at rest, so a failed or aborted deploy leaves no plaintext secrets on disk.

Verification

Reproduced and verified against the real built package on Linux (the bug does not reproduce on macOS):

Scenario Before After
Real varlock-wrangler deploy, 320 runs, 4 parallel 8 INVALID (~2.5%) — bytes = 2×/6×/7× payload 320/320 OK
varlock-wrangler types (wrangler re-reads the env file) hangs / times out generates worker-configuration.d.ts
Repeated reads against one serving (dev re-read path), 200 × 3 200/200 OK ×3

All 21 cloudflare wrangler framework tests pass (including types generation, which caught an earlier pure-single-shot attempt that didn't re-arm). typecheck and lint pass. Changeset included (@varlock/cloudflare-integration: patch).

Testing notes

No dedicated deploy-concatenation test is added. The fix lives in the shared FIFO serving code used by deploy, types, and dev, and the existing types/dev framework scenarios already exercise it end-to-end with real wrangler — the types scenario caught the re-arm regression during development. A deploy-specific guard would only be meaningful on Linux and is inherently probabilistic (a scheduling race), so it would risk silently passing on broken code while adding maintenance surface; the existing coverage plus the documented invariant in startServing is the better trade-off.

The FIFO that feeds resolved env to wrangler re-armed a new writer in a
tight loop. A FIFO is an unframed byte stream and wrangler reads the
--secrets-file with readFileSync (read-until-EOF), where EOF only occurs
once no writer holds the pipe open. Because the loop re-opened a writer
immediately after each close, a reader could read several concatenated
copies of the JSON before ever seeing EOF, so wrangler intermittently
reported the secrets-file contents as invalid (observed in Linux CI,
scheduling-dependent). Fixes #739.

Writers now serve exactly one copy then exit, guaranteeing a clean EOF
after one copy. The dev path, where wrangler re-reads the env file across
hot-reloads/restarts, opts into re-arming a fresh single-shot writer after
each read; only one writer is ever armed at a time, so copies never
overlap.

The FIFO is kept (rather than switching to a temp file) so resolved
secrets never exist as a file at rest — a failed or aborted deploy leaves
no plaintext secrets on disk.
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

patch Patch releases

  • @varlock/cloudflare-integration 1.1.6 → 1.1.7

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@varlock/cloudflare-integration@774

commit: 37147be

wrangler reads these files more than once — `wrangler types` re-reads the
--env-file, and the deploy/upload path can re-read the --secrets-file — so a
pure one-shot writer left the second read hanging (the cloudflare `types`
framework test timed out). Always re-arm a fresh single-shot writer once the
previous copy is consumed, for every command rather than dev only. Still only
one writer is armed at a time, so copies never overlap (no concatenation), and
secrets never touch disk.
@theoephraim theoephraim merged commit 5ae2f49 into main Jun 11, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@varlock/cloudflare-integration: varlock-wrangler deploy can pass invalid --secrets-file contents when using FIFO in CI

1 participant