From eb7eb7f27be088e08735814d1b2d7149a55ba94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 23 Apr 2026 16:03:21 -0400 Subject: [PATCH] Skip wrangler R2 calls when S3 credentials are present Win release host's wrangler OAuth is logged into a different Cloudflare account than the R2-enabled one, so `wrangler r2 bucket info` returns "Please enable R2 through the Cloudflare Dashboard" and release-local-winlinux fails before any upload. The S3 API credentials (R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY) carry their own account scope and are the path we already use for every upload and purge, so when they are set we can short-circuit the wrangler bucket-info/create and dev-url dance entirely. Also requires R2_PUBLIC_BASE_URL in .env.release.local when S3 mode is on, since the wrangler-based dev-url fallback is unreachable in that mode. --- scripts/release-local/r2.mjs | 47 ++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/scripts/release-local/r2.mjs b/scripts/release-local/r2.mjs index 80dd2955..dd02b45f 100644 --- a/scripts/release-local/r2.mjs +++ b/scripts/release-local/r2.mjs @@ -64,27 +64,48 @@ function resolveUpdateUrl(publicBaseUrl, releasePrefix) { return prefix ? `${baseUrl}/${prefix}` : baseUrl; } +function hasS3Credentials() { + return Boolean( + process.env.R2_ACCESS_KEY_ID?.trim() && + process.env.R2_SECRET_ACCESS_KEY?.trim(), + ); +} + export async function prepareR2ReleaseHost({ rootDir }) { const prefix = normalizePathSegment(process.env.AGENTCOMPANY_RELEASE_PREFIX?.trim() || defaultPrefix); - const whoami = JSON.parse((await runWrangler(["whoami", "--json"], rootDir, true)).stdout); - const accountId = process.env.R2_ACCOUNT_ID?.trim() + const s3Mode = hasS3Credentials(); + + let accountId = process.env.R2_ACCOUNT_ID?.trim() || process.env.AGENTCOMPANY_R2_ACCOUNT_ID?.trim() - || whoami.accounts?.[0]?.id; + || ""; if (!accountId) { - throw new Error("Unable to resolve Cloudflare account ID from wrangler."); + if (s3Mode) { + throw new Error("R2 S3 credentials set but R2_ACCOUNT_ID missing. Add it to .env.release.local."); + } + const whoami = JSON.parse((await runWrangler(["whoami", "--json"], rootDir, true)).stdout); + accountId = whoami.accounts?.[0]?.id; + } + if (!accountId) { + throw new Error("Unable to resolve Cloudflare account ID. Set R2_ACCOUNT_ID in .env.release.local."); } const bucket = process.env.R2_BUCKET?.trim() || process.env.AGENTCOMPANY_R2_BUCKET?.trim() || defaultBucket; - try { - await runWrangler(["r2", "bucket", "info", bucket, "--json"], rootDir, true); - } catch (error) { - if (!String(error).includes("does not exist")) { - throw error; + // Skip wrangler bucket-info/create when S3 creds are available: wrangler's + // OAuth may be logged into a different Cloudflare account (see Win host + // where the OAuth account has no R2 enabled). S3 API operations later in + // the flow cover the real bucket existence check. + if (!s3Mode) { + try { + await runWrangler(["r2", "bucket", "info", bucket, "--json"], rootDir, true); + } catch (error) { + if (!String(error).includes("does not exist")) { + throw error; + } + await runWrangler(["r2", "bucket", "create", bucket], rootDir, false); } - await runWrangler(["r2", "bucket", "create", bucket], rootDir, false); } let publicBaseUrl = process.env.R2_PUBLIC_BASE_URL?.trim() @@ -92,6 +113,12 @@ export async function prepareR2ReleaseHost({ rootDir }) { || ""; if (!publicBaseUrl) { + if (s3Mode) { + throw new Error( + "R2 S3 credentials set but R2_PUBLIC_BASE_URL missing. Add it to .env.release.local " + + "(e.g. R2_PUBLIC_BASE_URL=https://pub-.r2.dev).", + ); + } const devUrlState = await runWrangler(["r2", "bucket", "dev-url", "get", bucket], rootDir, true) .then((result) => result.stdout) .catch(() => "");