Dashboard › craft › Session 0ynXPQG9zW13
0ynXPQG9zW13HC1CAResearch factual questions about Cloudflare wrangler CLI v4.x behavior (specifically around wrangler pages deploy and wrangler deploy). I need accurate, verifiable answers grounded in Cloudflare's official docs and wrangler's actual behavior. Do NOT guess β cite Cloudflare docs URLs where possible. You can also install wrangler locally to test (cd /tmp/opencode && mkdir -p wr && cd wr && npm init -y && npm install wrangler@4.111.0) and run npx wrangler pages deploy --help, npx wrangler deploy --help, npx wrangler whoami --help, etc. to inspect flags/behavior.
Answer these specific questions:
Production branch default: For wrangler pages deploy, when deploying to production, what does Cloudflare actually use as the "production branch"? Is the conventional/default production branch for a Pages project main or production? When you create a Pages project via wrangler pages project create, what is the default production branch it sets? Does passing --branch main vs --branch production matter β i.e., does --branch <X> deploy to production ONLY if <X> exactly matches the project's configured production branch, otherwise a preview deploy? Confirm the exact semantics of --branch in wrangler pages deploy. (Context: our docs say default main β reviewer asks why not production.)
CLOUDFLARE_ACCOUNT_ID auto-discovery: Does wrangler auto-discover the account ID if CLOUDFLARE_ACCOUNT_ID is not set? Specifically:
CLOUDFLARE_ACCOUNT_ID being set?CLOUDFLARE_ACCOUNT_ID is unset (does it error and list them / prompt)?wrangler.toml / wrangler.jsonc config file instead of the env var? (for the worker case especially)CLOUDFLARE_ACCOUNT_ID strictly required, or optional-with-fallback?...
I'll research these questions using both the local wrangler CLI and Cloudflare's official docs. Let me start both in parallel.
installed 4.111.0
Now let me inspect the CLI help output in parallel and fetch the relevant docs.
wrangler pages deploy [directory]
Deploy a directory of static assets as a Pages deployment
POSITIONALS
directory The directory of static files to upload [string]
GLOBAL FLAGS
--cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string]
--env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array]
-h, --help Show help [boolean]
--install-skills Install Cloudflare skills for detected AI coding agents before running the command [boolean] [default: false]
--profile Use a specific auth profile [string]
-v, --version Show version number [boolean]
OPTIONS
--project-name The name of the project you want to deploy to [string]
--branch The name of the branch you want to deploy to [string]
--commit-hash The SHA to attach ...
=========== WHOAMI ===========
wrangler whoami
π΅οΈ Retrieve your user information
GLOBAL FLAGS
-c, --config Path to Wrangler configuration file [string]
--cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string]
-e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string]
--env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array]
-h, --help Show help [boolean]
--install-skills Install Cloudflare skills for detected AI coding agents before running the command [boolean] [default: false]
-v, --version Show version number [boolean]
OPTIONS
--account Show membership information for the given account (id or name). [string]
--json Return user information as JSON. ...
Good CLI evidence gathered. Now let me examine the wrangler source code for the actual behavior logic and fetch the official docs.
=== search for productionBranch/isProduction logic in pages deploy === node_modules/wrangler/wrangler-dist/cli.js node_modules/wrangler/wrangler-dist/cli.d.ts --- isProduction ? "production" : "preview" isProduction = project.production_branch === branch isProduction = true preview deployment production branch production branch: production branch name: production branch of your project production branch. Specify the production branch for your new Pages project when prompted, or re-run with the required information. production branch suggestion... production branch. Use --production-branch <branch> to specify the production branch for your Pages project.
=== account id discovery / multiple accounts ===
account_id
account_id ===
account_id ? {
account_id) {
account_id,
account_id,
account_id:
account_id:
account_id;
account_id>\
account_id\
account_id}
account_id) > -1,
account_id}/abuse-reports/${reportType}
account_id}/access/bookmarks
account_id}/access/bookmarks/${bookmarkId}
account_id}/access/custom_pages
account_id}/access/custom_pages/${customPageId}
account_id}/access/gateway_ca
account_id}/access/gateway_ca/${certificateId}
account_id}/access/identity_providers/${identityProviderId}/scim/groups
account_id}/access/identity_providers/${identityProviderId}/scim/users
account_id}/access/keys
account_id}/access/keys/rotate
account_id}/access/logs/access_requests
account_id}/access/logs/scim/updates
account_id}/access/organizations/doh
account_id}/access/policies
account_id}/access/policies/${policyId}
account_id}/access/policy-tests
account_id}/access/policy-tests/${policyTestId}
account_id}/access/policy-tests/${policyTestId}/...
The key finding: isProduction = project.production_branch === branch. Let me get more precise context on that logic and the account discovery flow.
=== isProduction context ===
=== branch default / git detection in pages deploy ===
The regex likely failed due to newlines. Let me use ripgrep with context flags.
298961:isProduction = project.production_branch === branch
=== with surrounding lines (byte-based) ===
isProduction = true;
isProduction = project.production_branch === branch;
isProduction ? "production" : "preview";
=== branch determination ===
branch ?? "not set"}, commitHash: ${commitHash ?? "not set"}, commitDirty: ${commitDirty ?? "not set"}`
maxFileCountAllowedFromClaims = /* @__PURE__ */ __name((token) => {
if (typeof vitest !== "undefined" && (token === "<<funfetti-auth-jwt>>" || token === "<<funfetti-auth-jwt2>>" || token === "<<aus-completion-token>>")) {
return MAX_ASSET_COUNT_DEFAULT;
}
try {
const decodedJwt = decodeJwtPayload(token);
const maxFileCountAllowed = decodedJwt["max_file_count_allowed"];
if (typeof maxFileCountAllowed == "number") {
return maxFileCountAllowed;
}
return MAX_ASSET_COUNT_DEFAULT;
} catch (e9) {
if (e9 instanceof Error) {
throw new Error(`Invalid token: ${e9.message}`);
}
return MAX_ASSET_COUNT_DEFAULT;
}
}, "maxFileCountAllowedFromClaims");
__name(renderProgress, "renderProgress");
}
});
async function deploy2({
directory,
accountId,
projectName,
branch,
skipCaching,
commitMessage,
commitHash,
commitDirty,
functionsDirectory: ...
This is the definitive answer for Q1 and Q4. The critical logic:
let isProduction = true;
if (branch) {
isProduction = project.production_branch === branch;
}
When --branch is NOT passed at all β isProduction = true (production deploy). When --branch IS passed, it's production only if it exactly matches the project's configured production_branch. Let me now find where branch gets its value earlier (git auto-detection) and the account discovery logic.
301838:let branch === find the pages deploy handler that calls deploy2 === 299984:args.branch
if (!Object.keys(aliases2).includes(args.path)) {
throw new Error("unrecognized module: " + args.path);
}
return {
path: aliases2[args.path],
external: true
};
});
}
};
}
var init_alias_external = __esm({
"src/deployment-bundle/esbuild-plugins/alias-external.ts"() {
init_import_meta_url();
__name(esbuildAliasExternalPlugin, "esbuildAliasExternalPlugin");
}
});
function getPagesEnvironmentVariables(projectName) {
let branch = "local";
try {
branch = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
encoding: "utf-8",
stdio: "pipe"
}).trim();
} catch {
}
let commitSha = "0000000000000000000000000000000000000000";
try {
commitSha = childProcess.execSync("git rev-parse HEAD", {
encoding: "utf-8",
stdio: "pipe"
}).trim();
} catch {
}
let shortSha = commitSha.substring(0, 8);
try {
shortSha = childProcess.execSync("git rev-parse ...
Note that getPagesEnvironmentVariables is for setting CF_PAGES_BRANCH env vars, not for determining the deploy branch. Let me find where the actual --branch value gets defaulted in the deploy handler (the deploymentResponse / branch: args.branch path near line 299984).
description: "The name of your Pages project",
demandOption: true
},
yes: {
alias: "y",
type: "boolean",
description: 'Answer "yes" to confirm project deletion'
}
},
positionalArgs: ["project-name"],
async handler(args) {
const config2 = getConfigCache(
PAGES_CONFIG_CACHE_FILENAME
);
const accountId = await requireAuth(config2);
const confirmed = args.yes || await confirm2(
`Are you sure you want to delete "${args.projectName}"? This action cannot be undone.`
);
if (confirmed) {
logger2.log("Deleting", args.projectName);
await fetchResult2(
COMPLIANCE_REGION_CONFIG_PUBLIC,
`/accounts/${accountId}/pages/projects/${args.projectName}`,
{ method: "DELETE" }
);
logger2.log("Successfully deleted", args.projectName);
}
}
});
}
});
// ...
init_dist2();
init_dist();
init_deploy3();
init_cfetch();
init_commands3();
init_config4();
init_config_cache();
init_create_command();
init_dialogs();
init_logger();
init_metrics2();
init_output();
init_user3();
init_friendly_validator_errors();
init_constants();
init_delegate_to_workers();
init_errors3();
init_projects2();
init_prompt_select_project();
init_run_workers_deploy();
init_utils13();
pagesDeploymentCreateCommand = createAlias({
aliasOf: "wrangler pages deploy"
});
pagesPublishCommand = createAlias({
aliasOf: "wrangler pages deploy",
metadata: {
deprecated: true,
hidden: true
}
});
pagesDeployCommand = createCommand({
metadata: {
description: "Deploy a directory of static assets as a Pages deployment",
status: "stable",
owner: "Workers: Authoring and Testing",
hideGlobalFlags: ["config", ...
Excellent β note the critical line: Use the --branch flag to target your production or preview branch. Let me continue reading to see the git-based branch defaulting logic.
const accountId = await requireAuth({
...configCache2,
...envAccountId ? { account_id: envAccountId } : {}
});
let projectName = args.projectName ?? config2?.name ?? configCache2.project_name;
let isExistingProject = true;
if (projectName) {
try {
await fetchResult2(
COMPLIANCE_REGION_CONFIG_PUBLIC,
`/accounts/${accountId}/pages/projects/${projectName}`
);
} catch (err) {
if (err.code !== 8000007) {
maybeWarnAboutIgnoredConfigFile(configPath, config2);
throw err;
} else {
isExistingProject = false;
}
}
}
const delegation = await maybeDelegatePagesToWorkers({
command: "deploy",
projectPath: process.cwd(),
assetsDirectory: directory,
accountHasPagesProjects: /* @__PURE__ */ __name(async () => (await listProjects({ ...
Critical finding for Q1: when creating a NEW project during pages deploy, the git detection is only a suggestion for the prompt, and the fallback default when not a git repo is "production" (defaultValue: productionBranch ?? "production"). Let me check pages project create's default and the branch-passing to deploy2.
})
}
);
saveToConfigCache(PAGES_CONFIG_CACHE_FILENAME, {
account_id: accountId,
project_name: projectName
});
logger2.log(`\u2728 Successfully created the '${projectName}' project.`);
sendMetricsEvent("create pages project");
break;
}
}
}
if (projectName && !isExistingProject && !isInteractive4) {
let message = `The Pages project "${projectName}" does not exist.`;
if (configPath && config2 === void 0) {
message += `
A configuration file was found at ${configPath} that does not appear to be for a Pages project (missing "pages_build_output_dir"). Did you mean to run \`wrangler deploy\` (to deploy a Worker) instead?`;
} else {
message += `
Maybe you intended to deploy a Worker project instead? ...
This confirms Q4 completely. The branch-defaulting logic:
if (isGitDir) β if no --branch, it reads git rev-parse --abbrev-ref HEAD as the branch.--branch β branch stays undefined β in deploy2, isProduction = true (production deploy).Let me confirm the else (non-git) path and then look at pages project create's default and account discovery.
if (!commitHash) {
commitHash = childProcess.execSync(`git rev-parse HEAD`).toString().trim();
logger2.debug(`pages deploy: Detected commit hash: "${commitHash}"`);
} else {
logger2.debug(
`pages deploy: Using provided commit hash: "${commitHash}"`
);
}
if (!commitMessage) {
commitMessage = childProcess.execFileSync("git", [
"show",
"-s",
"--format=%B",
commitHash
]).toString().trim();
logger2.debug(
`pages deploy: Detected commit message: "${commitMessage.substring(0, 50)}${commitMessage.length > 50 ? "..." : ""}"`
);
} else {
logger2.debug(`pages deploy: Using provided commit message`);
}
} catch (err) {
logger2.debug(
`pages deploy: Failed to detect git ...
Confirmed. In a non-git temp dir with no --branch, branch stays undefined, passed as undefined to deploy2, where isProduction = true. So a bare deploy from a non-git dir defaults to production. Now the account discovery and pages project create default.
=== requireAuth / account discovery === === 'More than one account' full === More than one account available but unable to select one in non-interactive mode.
112612: `More than one account available but unable to select one in non-interactive mode.
In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as \`account_id\` in your ${descriptor.getConfigFileLabel()} file.
Alternatively, try running \`${descriptor.commands.login}\` to re-authenticate.`,
{ telemetryMessage: "user account fetch empty" }
);
}
return usableAccounts;
}
__name(fetchAllAccounts2, "fetchAllAccounts");
__name4(fetchAllAccounts2, "fetchAllAccounts");
function getActiveAccountId(config2) {
const temporaryAccount = oauthFlow.getActiveTemporaryAccount();
if (temporaryAccount) {
return temporaryAccount.account.id;
}
if (config2.account_id) {
return config2.account_id;
}
const envAccountId = getCloudflareAccountIdFromEnv();
if (envAccountId) {
return envAccountId;
}
return getAccountFromCache2()?.id;
}
__name(getActiveAccountId, "getActiveAccountId");
__name4(getActiveAccountId, ...
This is the complete answer for Q2. The account-ID resolution order in getActiveAccountId / getOrSelectAccountId2:
config.account_id (from wrangler.toml/jsonc) β 3. CLOUDFLARE_ACCOUNT_ID env β 4. cached account β 5. fetch all accounts: if exactly one, auto-use it (and cache); if multiple in non-interactive mode β errors and lists them.Now the auth mechanisms (Q3) β let me check for global API key support and the pages project create default branch.
=== auth mechanisms: global API key + email === === getAuthFromEnv-ish ===
=== pages project create handler default production branch === 299764: description: "Create a new Cloudflare Pages project",
=== global key search variants ===
16 apiEmail
3 ApiEmail
6 authEmail
3 AuthEmail
10 authKey
15 AuthKey
1 CF_API_KEY
6 CLOUDFLARE_API_KEY
5 CLOUDFLARE_EMAIL
3 globalApiKey
2 Global API Key
2 x-auth-email
5 X-Auth-Email
2 x-auth-key
5 X-Auth-Key
return results;
}, "listProjects");
pagesProjectCreateCommand = createCommand({
metadata: {
description: "Create a new Cloudflare Pages project",
status: "stable",
owner: "Workers: Authoring and Testing",
hideGlobalFlags: ["config", "env"]
},
behaviour: {
provideConfig: false
},
args: {
"project-name": {
type: "string",
demandOption: true,
description: "The name of your Pages project"
},
"production-branch": {
type: "string",
description: "The name of the production branch of your project"
},
"compatibility-flags": {
description: "Flags to use for compatibility checks",
alias: "compatibility-flag",
type: "string",
requiresArg: true,
array: true
},
"compatibility-date": {
description: "Date to use for compatibility checks",
type: ...
Confirmed: wrangler DOES support the Global API Key (CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL) as an alternative auth. Let me see the pages project create default production branch and the auth-from-env precedence.
}
if (!projectName) {
throw new UserError(
"Missing Pages project name. Provide the project name as a positional argument: wrangler pages project create <name>.",
{ telemetryMessage: "pages projects create missing project name" }
);
}
if (!productionBranch && isInteractive4) {
logger2.debug(
"pages project create: Detecting git repository for production branch suggestion..."
);
let isGitDir = true;
try {
childProcess.execSync(`git rev-parse --is-inside-work-tree`, {
stdio: "ignore"
});
logger2.debug("pages project create: Git repository detected");
} catch (err) {
isGitDir = false;
logger2.debug(
`pages project create: Not a git repository: ${err instanceof Error ? err.message : String(err)}`
);
}
if (isGitDir) {
try {
...
Definitive for Q1's "default production branch" question:
wrangler pages project create in interactive mode: git-detected current branch is offered as a suggestion; if not a git dir, the prompt default is "production". In non-interactive mode with no --production-branch, it errors (no silent default).git rev-parse --abbrev-ref HEAD on most modern repos returns main (or whatever the current branch is) β so the suggested branch depends on the repo, NOT a hardcoded main.Now let me verify the env-var auth precedence and then cross-check with the official docs.
108093: * @param {string | null | undefined} [opts.apiKey=process.env['CLOUDFLARE_API_KEY'] ?? null]
108105: constructor({ baseURL = readEnv("CLOUDFLARE_BASE_URL"), apiVersion = null, apiToken = readEnv("CLOUDFLARE_API_TOKEN") ?? null, apiKey = readEnv("CLOUDFLARE_API_KEY") ?? null, apiEmail = readEnv("CLOUDFLARE_EMAIL") ?? null, userServiceKey = readEnv("CLOUDFLARE_API_USER_SERVICE_KEY") ?? null, ...opts } = {}) {
111004: variableName: "CLOUDFLARE_API_KEY",
fs6.rmSync(encryptedPath);
}
return { backendAvailable: false, encryptedFileExisted };
}
var import_undici2, hasWarnedAboutDeprecatedV1ApiToken, getCloudflareAPITokenFromEnv, getCloudflareGlobalAuthKeyFromEnv, getCloudflareGlobalAuthEmailFromEnv, getAuthDomainFromEnv, getAuthUrlFromEnv, getTokenUrlFromEnv, getRevokeUrlFromEnv, getCloudflareAccountIdFromEnv, getAccessClientIdFromEnv, getAccessClientSecretFromEnv, getCfAuthorizationTokenFromEnv, getCloudflareAuthUseKeyringFromEnv, headersCache, usesAccessCache, RECOMMENDED_CODE_VERIFIER_LENGTH, RECOMMENDED_STATE_LENGTH, PKCE_CHARSET, generateAuthUrl, POW_MAX_ITERATIONS, TEMPORARY_TERMS_URLS, TEMPORARY_TERMS_PROMPT, TEMPORARY_TERMS_NOTICE, TEMPORARY_TERMS_ERROR, esm_default3, ErrorOAuth2, ErrorUnknown, ErrorNoAuthCode, ErrorInvalidReturnedStateParam, ErrorInvalidJson, ErrorInvalidScope, ErrorInvalidRequest, ErrorInvalidToken, ErrorAuthenticationGrant, ErrorUnauthorizedClient, ErrorAccessDenied, ErrorUnsupportedResponseType, ...
All auth env vars confirmed: CLOUDFLARE_API_TOKEN (deprecated CF_API_TOKEN), CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL (Global API Key auth), plus OAuth via wrangler login. Now let me cross-check with the official Cloudflare docs to cite URLs.
--- title: System environment variables description: Local environment variables that can change Wrangler's behavior. image: https://developers.cloudflare.com/dev-products-preview.png --- > Documentation Index > Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt > Use this file to discover all available pages before exploring further. [Skip to content](#%5Ftop) # System environment variables System environment variables are local environment variables that can change Wrangler's behavior. There are three ways to set system environment variables: 1. Create an `.env` file in your project directory. Set the values of your environment variables in your [.env](https://developers.cloudflare.com/workers/wrangler/system-environment-variables/#example-env-file) file. This is the recommended way to set these variables, as it persists the values between Wrangler sessions. 2. Inline the values in your Wrangler command. ...
--- title: Commands description: Create, develop, and deploy your Cloudflare Workers with Wrangler commands. image: https://developers.cloudflare.com/dev-products-preview.png --- > Documentation Index > Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt > Use this file to discover all available pages before exploring further. [Skip to content](#%5Ftop) # Commands [Wrangler](https://developers.cloudflare.com/workers/wrangler/) offers a number of commands to manage your Cloudflare Workers. ## Workers commands The core Wrangler commands for creating, developing, and deploying Workers are on the [Workers commands page](https://developers.cloudflare.com/workers/wrangler/commands/workers/). This includes `wrangler dev`, `wrangler deploy`, `wrangler versions`, and more. ## All commands * [ Artifacts ](https://developers.cloudflare.com/workers/wrangler/commands/artifacts/) * [ Browser ...
Official docs confirm all env vars and that CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN are optional. Now let me fetch the Pages commands doc and the git-integration doc that describes production-branch semantics, plus check for OIDC/keyless GitHub Actions options.
--- title: Pages description: Wrangler commands for configuring Cloudflare Pages. image: https://developers.cloudflare.com/dev-products-preview.png --- > Documentation Index > Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt > Use this file to discover all available pages before exploring further. [Skip to content](#%5Ftop) # Pages Configure [Cloudflare Pages](https://developers.cloudflare.com/pages/) using Wrangler. ## `pages dev` Develop your full-stack Pages application locally * [ npm ](#tab-panel-13363) * [ pnpm ](#tab-panel-13364) * [ yarn ](#tab-panel-13365) ```sh npx wrangler pages dev [DIRECTORY] [COMMAND] ``` ```sh pnpm wrangler pages dev [DIRECTORY] [COMMAND] ``` ```sh yarn wrangler pages dev [DIRECTORY] [COMMAND] ``` * `[DIRECTORY]` ` string ` The directory of static assets to serve * `[COMMAND]` ` string ` The proxy command to run \[deprecated\] * `--compatibility-date` ` string ` Date to use for ...
--- title: Branch deployment controls description: Control which branches trigger automatic deployments in your Cloudflare Pages project. image: https://developers.cloudflare.com/dev-products-preview.png --- > Documentation Index > Fetch the complete documentation index at: https://developers.cloudflare.com/pages/llms.txt > Use this file to discover all available pages before exploring further. [Skip to content](#%5Ftop) # Branch deployment controls When connected to your git repository, Pages allows you to control which environments and branches you would like to automatically deploy to. By default, Pages will trigger a deployment any time you commit to either your production or preview environment. However, with branch deployment controls, you can configure automatic deployments to suit your preference on a per project basis. ## Production branch control Direct Upload If your project is a [Direct ...
Key doc confirmation for Q1: "Pages will default to setting your production environment to the branch you first push" β there is no universal main or production default; it's whatever branch you first deploy/push. For Direct Upload projects, the production branch is set on project creation.
Let me check the Craft implementation itself to make specific improvement recommendations, and check the current default in the code.
Found 40 matches /home/byk/Code/getsentry/craft/src/targets/cloudflare.ts: Line 29: 'CLOUDFLARE_API_TOKEN', Line 30: 'CLOUDFLARE_ACCOUNT_ID', Line 51: /** Default production branch passed to `wrangler pages deploy --branch` */ Line 66: productionBranch?: string; Line 78: * The Cloudflare Pages project's production branch name. Passed to Line 79: * `wrangler pages deploy --branch` so a release publish always targets the Line 80: * production environment. This is the Cloudflare environment selector, NOT Line 83: productionBranch: string; Line 149: // environment -- which includes CLOUDFLARE_API_TOKEN/ACCOUNT_ID. Reject Line 153: productionBranch: config.productionBranch, Line 171: productionBranch: config.productionBranch || DEFAULT_PRODUCTION_BRANCH, Line 213: // Pages deploy. `--branch <productionBranch>` forces a production Line 222: '--branch', Line 223: ...
<path>/home/byk/Code/getsentry/craft/src/targets/cloudflare.ts</path>
<type>file</type>
<content>
20: import { BaseArtifactProvider } from '../artifact_providers/base';
21:
22: /**
23: * Secrets required to authenticate with the Cloudflare API.
24: *
25: * Exported so tests (and documentation tooling) can reference the canonical
26: * list of environment variables this target consumes.
27: */
28: export const targetSecrets = [
29: 'CLOUDFLARE_API_TOKEN',
30: 'CLOUDFLARE_ACCOUNT_ID',
31: ] as const;
32: type SecretsType = (typeof targetSecrets)[number];
33:
34: /** Wrangler executable configuration */
35: const WRANGLER_CONFIG = {
36: name: 'wrangler',
37: envVar: 'WRANGLER_BIN',
38: errorHint:
39: 'Install wrangler (npm install -g wrangler) or set WRANGLER_BIN to its path',
40: } as const;
41:
42: /** How the artifact should be deployed to Cloudflare */
43: type CloudflareDeployType = 'pages' | 'worker';
44:
45: /** Valid deploy types */
46: const DEPLOY_TYPES: ...
I now have everything needed. Let me verify the account ID is treated as a required secret in the code (line 149 context) to make my improvement recommendation precise.
<path>/home/byk/Code/getsentry/craft/src/targets/cloudflare.ts</path>
<type>file</type>
<content>
140:
141: if (deployType === 'pages' && !config.projectName) {
142: throw new ConfigurationError(
143: '[cloudflare] "projectName" is required when deployType is "pages"',
144: );
145: }
146:
147: // These config values are passed to wrangler as command-line arguments.
148: // spawnProcess() expands args of the exact form "${VAR}" using the
149: // environment -- which includes CLOUDFLARE_API_TOKEN/ACCOUNT_ID. Reject
150: // such values so a config string can never be expanded into a secret.
151: for (const [key, value] of Object.entries({
152: projectName: config.projectName,
153: productionBranch: config.productionBranch,
154: workingDir: config.workingDir,
155: })) {
156: if (typeof value === 'string' && /^\$\{.*\}$/.test(value)) {
157: throw new ConfigurationError(
158: `[cloudflare] ...
I have complete evidence. Here's the full research report.
All CLI evidence gathered from a locally installed wrangler@4.111.0 (/tmp/opencode/wr), reading its bundled source at node_modules/wrangler/wrangler-dist/cli.js. Doc citations from Cloudflare's official docs.
--branch semanticsThe --branch semantics (definitive, from wrangler source, deploy2() in cli.js:298960):
let isProduction = true;
if (branch) {
isProduction = project.production_branch === branch;
}
const env7 = isProduction ? "production" : "preview";
--branch is passed (and not resolved from git) β isProduction = true β production deploy.--branch <X> is passed β it's a production deploy only if <X> exactly matches the project's server-side configured production_branch; otherwise it's a preview deploy.This is an exact-string match against the project's stored production_branch, fetched from the API (GET /accounts/{id}/pages/projects/{name}). So --branch main vs --branch production matters entirely based on what the project was configured with β neither is privileged by wrangler.
Is the default main or production? Neither is a hardcoded universal default. Evidence:
wrangler pages project create (source cli.js:299802): the production branch comes from --production-branch, or in interactive mode a prompt whose default is the git-detected current branch (git rev-parse --abbrev-ref HEAD), falling back to the literal string "production" when not in a git repo:
productionBranch = ...
<observations> Date: Jul 21, 2026 * π‘ (09:09) [tool:result] Cloudflare docs "Pages" page (wrangler command reference) confirms `wrangler pages dev [DIRECTORY] [COMMAND]` flags: `--compatibility-date`, `--compatibility-flags` (alias `--compatibility-flag`), `--ip`, `--port`, `--inspector-port`, `--binding`/`--b`, `--r2`, `--live-reload` (default: false), `--persist-to`. * π‘ (09:09) [tool:resultβ¦
<observations> Date: Jul 21, 2026 * π‘ (09:08) [tool:result] Found `wrangler pages project create` source: if `--production-branch` not provided in non-interactive mode, throws UserError "Missing production branch. Use --production-branch <branch> to specify the production branch for your Pages project." (no silent default in non-interactive mode). Request body sent to `POST /accounts/${accountIβ¦
<observations> Date: Jul 21, 2026 * π‘ (09:03) [tool:result] Confirmed wrangler 4.111.0 installed successfully in /tmp/opencode/wr. * π‘ (09:03) [tool:result] `wrangler pages deploy --help` output captured. Key OPTIONS: --project-name, --branch ("The name of the branch you want to deploy to"), --commit-hash, --commit-message, --commit-dirty, --skip-caching, --no-bundle, --upload-source-maps (defaβ¦
<observations> Date: Jul 21, 2026 * π΄ (09:02) User requested research on Cloudflare `wrangler` CLI v4.x behavior, specifically `wrangler pages deploy` and `wrangler deploy`, requiring answers grounded in official Cloudflare docs with citations/URLs where possible, explicitly stating not to guess. * π΄ (09:02) User instructed to install wrangler locally for testing via: `cd /tmp/opencode && mkdirβ¦