From 001b32e00c6060cd597ac48a09fffca597b44d1e Mon Sep 17 00:00:00 2001 From: 2xburnt <169301814+2xburnt@users.noreply.github.com> Date: Sat, 22 Aug 2026 05:43:52 +0200 Subject: [PATCH 1/3] Tolerate previews of Workers that cannot have preview URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloudflare never generates preview URLs for Workers that implement a Durable Object, Containers included — `versions upload` succeeds with a Version ID and no URL. The metadata step treated the missing URL as missing metadata and failed the whole preview, which makes cloudflare-pr unusable for any DO/Container consumer (first hit: burnt-labs/ai-gateway PR #9, whose Worker hosts two Container classes). A preview with no URL now falls back to the target URL and carries an explanatory note: a new preview-note output travels through cloudflare-version.yml to the PR comment and the step summary, so the link is never mistaken for an isolated preview. Deploys are unchanged, and a missing Version ID still fails. --- .github/workflows/cloudflare-pr.yml | 2 ++ .github/workflows/cloudflare-version.yml | 20 +++++++++++++++++++ tests/workflows.test.mjs | 25 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/.github/workflows/cloudflare-pr.yml b/.github/workflows/cloudflare-pr.yml index cf1e8e6..89ccbc7 100644 --- a/.github/workflows/cloudflare-pr.yml +++ b/.github/workflows/cloudflare-pr.yml @@ -83,6 +83,7 @@ jobs: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: PREVIEW_URL: ${{ needs.preview-candidate.outputs.deployment-url }} + PREVIEW_NOTE: ${{ needs.preview-candidate.outputs.preview-note }} RELEASE_PREFIX: ${{ fromJSON(needs.quality.outputs.deployment-policy).releasePrefix }} VERSION_ID: ${{ needs.preview-candidate.outputs.version-id }} with: @@ -96,6 +97,7 @@ jobs: marker, `### ${heading}`, `- Preview: ${process.env.PREVIEW_URL}`, + ...(process.env.PREVIEW_NOTE ? [`- Note: ${process.env.PREVIEW_NOTE}`] : []), `- Cloudflare version: \`${process.env.VERSION_ID}\``, `- Commit: \`${context.sha}\``, ].join('\n') diff --git a/.github/workflows/cloudflare-version.yml b/.github/workflows/cloudflare-version.yml index 83d367d..3d39db5 100644 --- a/.github/workflows/cloudflare-version.yml +++ b/.github/workflows/cloudflare-version.yml @@ -43,6 +43,10 @@ on: value: ${{ jobs.version.outputs.version-id }} deployment-url: value: ${{ jobs.version.outputs.deployment-url }} + # Non-empty only when a preview produced no preview URL (Durable Object + # Workers never get one) and deployment-url fell back to the target. + preview-note: + value: ${{ jobs.version.outputs.preview-note }} permissions: contents: read @@ -60,6 +64,7 @@ jobs: outputs: version-id: ${{ steps.metadata.outputs.version-id }} deployment-url: ${{ steps.metadata.outputs.deployment-url }} + preview-note: ${{ steps.metadata.outputs.preview-note }} defaults: run: working-directory: ${{ fromJSON(inputs.quality-policy).workingDirectory }} @@ -237,8 +242,19 @@ jobs: run: | VERSION_ID=$(printf '%s' "$COMMAND_OUTPUT" | grep -oE '(Worker )?Version ID: +[a-f0-9-]+' | head -1 | grep -oE '[a-f0-9-]{8,}' || true) PREVIEW_URL=$(printf '%s' "$COMMAND_OUTPUT" | grep -oE 'https://[^[:space:]]+\.workers\.dev[^[:space:]]*' | head -1 || true) + PREVIEW_NOTE="" if [ "$OPERATION" = "preview" ]; then DEPLOYMENT_URL="${PREVIEW_URL:-$ACTION_URL}" + if [ -z "$DEPLOYMENT_URL" ]; then + # Cloudflare does not issue preview URLs for Workers that + # implement a Durable Object, Containers included — the upload + # succeeded and the version exists at 0% traffic, there is just + # no URL that serves it. Fall back to the target URL so the + # GitHub Environment and the PR comment point somewhere real, + # and carry a note so nobody mistakes it for the preview. + DEPLOYMENT_URL="$TARGET_URL" + PREVIEW_NOTE="no preview URL: Cloudflare does not generate them for Durable Object Workers; the version is uploaded at 0% traffic and the URL above is the live target" + fi else DEPLOYMENT_URL="${TARGET_URL:-$ACTION_URL}" fi @@ -248,10 +264,14 @@ jobs: fi echo "version-id=$VERSION_ID" >> "$GITHUB_OUTPUT" echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" + echo "preview-note=$PREVIEW_NOTE" >> "$GITHUB_OUTPUT" { echo "### Cloudflare ${{ inputs.target }} ${{ inputs.operation }}" echo "- Tag: \`${{ inputs.version-tag }}\`" echo "- Version: \`$VERSION_ID\`" echo "- URL: $DEPLOYMENT_URL" + if [ -n "$PREVIEW_NOTE" ]; then + echo "- Note: $PREVIEW_NOTE" + fi echo "- Source: ${{ inputs.version-message }}" } >> "$GITHUB_STEP_SUMMARY" diff --git a/tests/workflows.test.mjs b/tests/workflows.test.mjs index 1ec41e5..5a99e97 100644 --- a/tests/workflows.test.mjs +++ b/tests/workflows.test.mjs @@ -289,6 +289,31 @@ test("Wrangler metadata arguments remain single tokens", () => { ); }); +test("preview metadata tolerates Workers without preview URLs", () => { + // Cloudflare never generates preview URLs for Workers that implement a + // Durable Object (Containers included) — `versions upload` succeeds with a + // Version ID and no URL. The metadata step must fall back to the target URL + // with an explanatory note instead of failing the preview, and the note must + // travel to the caller so cloudflare-pr.yml can surface it in the comment. + const source = fs.readFileSync(`${directory}/cloudflare-version.yml`, "utf8"); + const workflow = parse(source); + const metadata = workflow.jobs.version.steps.find( + (step) => step.id === "metadata", + ); + assert.match(metadata.run, /DEPLOYMENT_URL="\$TARGET_URL"/); + assert.match(metadata.run, /PREVIEW_NOTE=/); + assert.match(metadata.run, /Durable Object/); + assert.equal( + workflow.on.workflow_call.outputs["preview-note"].value, + "${{ jobs.version.outputs.preview-note }}", + ); + const pr = parse(fs.readFileSync(`${directory}/cloudflare-pr.yml`, "utf8")); + const comment = pr.jobs["publish-preview"].steps.find( + (step) => step.env && step.env.PREVIEW_NOTE !== undefined, + ); + assert.ok(comment, "PR comment step must receive the preview note"); +}); + test("Worker secrets are allowlisted, never forwarded wholesale", () => { // toJSON(secrets) in the publish step contains every secret the caller // inherited, the Cloudflare API token included. The allowlist is the entire From ffc9220f81438c2537163f041f117d3097738fed Mon Sep 17 00:00:00 2001 From: 2xburnt <169301814+2xburnt@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:44:38 +0200 Subject: [PATCH 2/3] Group GITHUB_OUTPUT writes to satisfy shellcheck SC2129 --- .github/workflows/cloudflare-version.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cloudflare-version.yml b/.github/workflows/cloudflare-version.yml index 3d39db5..b528fe7 100644 --- a/.github/workflows/cloudflare-version.yml +++ b/.github/workflows/cloudflare-version.yml @@ -262,9 +262,11 @@ jobs: echo "::error::Cloudflare output did not contain version metadata" exit 1 fi - echo "version-id=$VERSION_ID" >> "$GITHUB_OUTPUT" - echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" - echo "preview-note=$PREVIEW_NOTE" >> "$GITHUB_OUTPUT" + { + echo "version-id=$VERSION_ID" + echo "deployment-url=$DEPLOYMENT_URL" + echo "preview-note=$PREVIEW_NOTE" + } >> "$GITHUB_OUTPUT" { echo "### Cloudflare ${{ inputs.target }} ${{ inputs.operation }}" echo "- Tag: \`${{ inputs.version-tag }}\`" From a5ca8e0d6cd0bf31395d61a40965399b48275454 Mon Sep 17 00:00:00 2001 From: 2xburnt <169301814+2xburnt@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:45:37 +0200 Subject: [PATCH 3/3] Advance internal pins to the v1.4.1 implementation --- .github/workflows/cloudflare-main.yml | 10 +++++----- .github/workflows/cloudflare-pr.yml | 4 ++-- .github/workflows/cloudflare-release.yml | 6 +++--- .github/workflows/npm-changesets.yml | 2 +- .github/workflows/npm-main.yml | 8 ++++---- .github/workflows/npm-pr.yml | 2 +- .github/workflows/npm-release.yml | 4 ++-- .github/workflows/phala-deploy.yml | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cloudflare-main.yml b/.github/workflows/cloudflare-main.yml index 27f6bbf..f375242 100644 --- a/.github/workflows/cloudflare-main.yml +++ b/.github/workflows/cloudflare-main.yml @@ -27,7 +27,7 @@ permissions: jobs: quality: - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy-path: ${{ inputs.quality-policy-path }} deployment-policy-path: ${{ inputs.deployment-policy-path }} @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: burnt-labs/github-workflows - ref: 46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + ref: ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 path: .burnt-workflows - name: Read stable release tags env: @@ -76,7 +76,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: # Under single topology the candidate and the release are the same # Worker, so deploying here would serve the merge immediately and there @@ -98,7 +98,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: operation: preview target: release @@ -184,7 +184,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: operation: deploy target: release diff --git a/.github/workflows/cloudflare-pr.yml b/.github/workflows/cloudflare-pr.yml index 89ccbc7..4ffebd7 100644 --- a/.github/workflows/cloudflare-pr.yml +++ b/.github/workflows/cloudflare-pr.yml @@ -31,7 +31,7 @@ jobs: github.event_name != 'pull_request' || (github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy-path: ${{ inputs.quality-policy-path }} deployment-policy-path: ${{ inputs.deployment-policy-path }} @@ -62,7 +62,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: operation: preview target: candidate diff --git a/.github/workflows/cloudflare-release.yml b/.github/workflows/cloudflare-release.yml index af8ca7d..2a6bbe1 100644 --- a/.github/workflows/cloudflare-release.yml +++ b/.github/workflows/cloudflare-release.yml @@ -56,7 +56,7 @@ jobs: quality: needs: validate - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy-path: ${{ inputs.quality-policy-path }} deployment-policy-path: ${{ inputs.deployment-policy-path }} @@ -89,7 +89,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: operation: preview target: release @@ -108,7 +108,7 @@ jobs: permissions: contents: read deployments: write - uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/cloudflare-version.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: operation: deploy target: release diff --git a/.github/workflows/npm-changesets.yml b/.github/workflows/npm-changesets.yml index d6db1ae..7709824 100644 --- a/.github/workflows/npm-changesets.yml +++ b/.github/workflows/npm-changesets.yml @@ -96,7 +96,7 @@ permissions: jobs: quality: - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@d169eff38ec93b6405f15a2b2dd86b4bcda21bcb # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy-path: ${{ inputs.quality-policy-path }} diff --git a/.github/workflows/npm-main.yml b/.github/workflows/npm-main.yml index 8bc69b9..a002eb6 100644 --- a/.github/workflows/npm-main.yml +++ b/.github/workflows/npm-main.yml @@ -8,7 +8,7 @@ permissions: jobs: quality: - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 metadata: needs: quality @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: burnt-labs/github-workflows - ref: 46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + ref: ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 path: .burnt-workflows - name: Read stable release tags env: @@ -107,7 +107,7 @@ jobs: permissions: contents: read id-token: write - uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy: ${{ needs.quality.outputs.quality-policy }} npm-policy: ${{ needs.quality.outputs.npm-policy }} @@ -171,7 +171,7 @@ jobs: permissions: contents: read id-token: write - uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy: ${{ needs.quality.outputs.quality-policy }} npm-policy: ${{ needs.quality.outputs.npm-policy }} diff --git a/.github/workflows/npm-pr.yml b/.github/workflows/npm-pr.yml index 5922095..948248f 100644 --- a/.github/workflows/npm-pr.yml +++ b/.github/workflows/npm-pr.yml @@ -8,7 +8,7 @@ permissions: jobs: quality: - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 pack: name: Package dry run diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml index 628d022..53eaa42 100644 --- a/.github/workflows/npm-release.yml +++ b/.github/workflows/npm-release.yml @@ -15,7 +15,7 @@ permissions: jobs: quality: - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 metadata: needs: quality @@ -52,7 +52,7 @@ jobs: permissions: contents: read id-token: write - uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/npm-publish.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 with: quality-policy: ${{ needs.quality.outputs.quality-policy }} npm-policy: ${{ needs.quality.outputs.npm-policy }} diff --git a/.github/workflows/phala-deploy.yml b/.github/workflows/phala-deploy.yml index 537da05..b86eb25 100644 --- a/.github/workflows/phala-deploy.yml +++ b/.github/workflows/phala-deploy.yml @@ -47,7 +47,7 @@ jobs: quality: needs: policy - uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@46ae18b0f5ff1735e07601e41a6044b358379853 # v1.4.0 + uses: burnt-labs/github-workflows/.github/workflows/required-quality.yml@ffc9220f81438c2537163f041f117d3097738fed # v1.4.1 deploy: name: Phala ${{ inputs.target }}