Skip to content

Make remote dev exchange_url optional#12771

Open
penalosa wants to merge 8 commits intomainfrom
penalosa/optional-remote-exchange
Open

Make remote dev exchange_url optional#12771
penalosa wants to merge 8 commits intomainfrom
penalosa/optional-remote-exchange

Conversation

@penalosa
Copy link
Contributor

@penalosa penalosa commented Mar 5, 2026

Depending on the zone settings, a fetch to exchange_url can sometimes fail. This PR makes that fetch optional, falling back to the raw token provided by the control plane. Additionally, it removes the experimental tail logs flag, which is now always on.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because: existing tests should pass
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: internal refactor

A picture of a cute animal (not mandatory, but encouraged)


Open with Devin

@penalosa penalosa requested review from a team as code owners March 5, 2026 14:32
@changeset-bot
Copy link

changeset-bot bot commented Mar 5, 2026

🦋 Changeset detected

Latest commit: fe70a72

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

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
Copy link
Contributor

workers-devprod commented Mar 5, 2026

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • packages/edge-preview-authenticated-proxy/src/index.ts: [@cloudflare/wrangler]
  • packages/edge-preview-authenticated-proxy/tests/index.test.ts: [@cloudflare/wrangler]
  • packages/playground-preview-worker/src/index.ts: [@cloudflare/wrangler]
  • packages/playground-preview-worker/src/realish.ts: [@cloudflare/wrangler]
  • packages/playground-preview-worker/src/user.do.ts: [@cloudflare/wrangler]
  • packages/workers-playground/src/QuickEditor/useDraftWorker.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/api/startDevWorker/RemoteRuntimeController.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/dev/remote-bindings-errors.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/ConfigController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/ProxyController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/RemoteRuntimeController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/types.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev/create-worker-preview.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev/hotkeys.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/dev/start-dev.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/pages/dev.ts: [@cloudflare/wrangler]
  • tools/deployments/validate-changesets.ts: [@cloudflare/wrangler]

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 String coercion of undefined name produces literal "undefined" in KV namespace name

The type of props.name was changed from string to string | undefined at packages/wrangler/src/dev/remote.ts:97, but the expression props.name + (props.useServiceEnvironments && props.env ? \-${props.env}` : "")at line 131 still uses string concatenation. In JavaScript,undefined + ""evaluates to the literal string"undefined". When a worker has no configured name (e.g. running wrangler devwithout a name in config), this will pass"undefined"(or"undefined-envName") as the scriptNametosyncWorkersSite`, potentially creating/syncing a KV namespace with that bogus name.

Previously this was not possible because the old code in RemoteRuntimeController computed a scriptId fallback (packages/wrangler/src/api/startDevWorker/RemoteRuntimeController.ts old lines 131-135) that derived a name from the session when props.name was falsy. That fallback was removed in this PR.

(Refers to lines 131-132)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

modules: props.modules,
accountId: props.accountId,
name: scriptId,
name: props.name,
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Removed scriptId fallback causes undefined to be interpolated into API URL paths

The old code in RemoteRuntimeController.#previewToken had a fallback: const scriptId = props.name || (workerContext.zone ? this.#session.id : this.#session.host.split(".")[0]) which ensured a valid script identifier was always used even when config.name was undefined. This PR removes that fallback and passes props.name (now string | undefined) directly through createRemoteWorkerInit to createPreviewToken (packages/wrangler/src/dev/create-worker-preview.ts:224-225), where worker.name is interpolated into API URL paths like /accounts/${accountId}/workers/scripts/${worker.name}/edge-preview. When name is undefined, this produces the literal URL segment "undefined", causing the API call to target a non-existent script named "undefined" and fail.

Prompt for agents
In packages/wrangler/src/api/startDevWorker/RemoteRuntimeController.ts, the #previewToken method (around line 136-151) now passes props.name directly to createRemoteWorkerInit. Previously (before this PR), there was a scriptId fallback computed as: const scriptId = props.name || (workerContext.zone ? this.#session.id : this.#session.host.split('.')[0]). This ensured a valid name was always used.

Since the PR removed session.id (from CfPreviewSession), a new fallback strategy is needed. Consider using the session host as a fallback, e.g.:
  const scriptName = props.name ?? this.#session.host.split('.')[0];
and pass scriptName instead of props.name to createRemoteWorkerInit at line 141.

Similarly, in packages/wrangler/src/dev/remote.ts line 131, the syncWorkersSite call uses props.name in string concatenation. If props.name can be undefined, this must be guarded (e.g. skip the call or use a fallback name).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 5, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@12771

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@12771

miniflare

npm i https://pkg.pr.new/miniflare@12771

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@12771

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@12771

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@12771

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@12771

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@12771

wrangler

npm i https://pkg.pr.new/wrangler@12771

commit: c61c7fa

penalosa added 3 commits March 5, 2026 14:52
…workers

- Fix test mocks to match new CfPreviewSession/CfPreviewToken shapes
- playground-preview-worker: make exchange_url optional, use initial
  token as fallback, remove inspector URL construction and prewarm
- playground frontend: remove inspector from response codec
- edge-preview-authenticated-proxy: remove /exchange CORS proxy
  endpoint (dead since DEVX-979), remove prewarm from
  .update-preview-token flow
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

✅ All changesets look good

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

🐛 1 issue in files not directly in the diff

🐛 Incomplete tailLogs removal enables inspector proxy in remote mode without an inspector URL (packages/wrangler/src/api/startDevWorker/ProxyController.ts:414-418)

The PR removed the --experimental-tail-logs CLI flag (which had default: true), and stopped setting input.experimental.tailLogs in start-dev.ts. However, the check at ProxyController.ts:417 still gates inspector behavior on !this.latestConfig?.experimental?.tailLogs. Since tailLogs is now always false (because ConfigController.ts:375 computes !!input.experimental?.tailLogs from the now-absent input), the inspectorEnabled getter returns true for remote mode — the opposite of the previous default behavior.

Before this PR: tailLogs defaulted to true!true = false → inspector disabled in remote mode.
After this PR: tailLogs is always false!false = true → inspector enabled in remote mode.

But the PR also removed userWorkerInspectorUrl from the proxyData emitted by RemoteRuntimeController (RemoteRuntimeController.ts:307). So the InspectorProxyWorker is now started (ProxyController.ts:139) and receives reloadComplete messages (ProxyController.ts:457-461) with no inspector URL to connect to, which is a regression from the previous default behavior.

View 6 additional findings in Devin Review.

Open in Devin Review

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +182 to +186
const subdomain = await getWorkersDevSubdomain(
complianceConfig,
account.accountId,
undefined
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 getWorkersDevSubdomain called without explicit API token, inconsistent with surrounding auth

In the new createPreviewSession, when ctx.host is not set (workers.dev preview), getWorkersDevSubdomain is called at line 182 to fetch the account's subdomain. However, this function calls fetchResult internally without an explicit apiToken parameter, relying on global auth context (env vars or ~/.wrangler/config). This is inconsistent with the preceding fetchResult call at create-worker-preview.ts:169 which explicitly passes apiToken from the account parameter.

In the old code, the host was derived from the exchange response, so no additional API call was needed. The new code introduces this dependency on global auth which can fail for programmatic API users (e.g., startRemoteProxySession) who provide auth explicitly via config.dev.auth without having global auth configured.

Prompt for agents
In packages/wrangler/src/dev/create-worker-preview.ts, the getWorkersDevSubdomain function at line 182 is called without the explicit apiToken that is available in the account parameter (line 161). Either:

1. Modify getWorkersDevSubdomain (defined in packages/wrangler/src/routes.ts at line 15) to accept an optional apiToken parameter and pass it through to its internal fetchResult call, then pass account.apiToken from createPreviewSession.

2. Or, create a local variant that accepts and passes the apiToken, to keep getWorkersDevSubdomain's existing signature for other callers.

The key fix is ensuring the fetchResult call inside getWorkersDevSubdomain uses the same explicit apiToken as the fetchResult call at line 166-169 of create-worker-preview.ts.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 15 additional findings in Devin Review.

Open in Devin Review

Comment on lines +77 to 81
async function tryExpandToken(exchangeUrl: string): Promise<string> {
const response = await fetch(exchangeUrl);
const json = (await response.json()) as { token: string };
return json.token;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 tryExpandToken silently returns undefined on malformed exchange response, overwriting valid initial token

In packages/playground-preview-worker/src/realish.ts, the new tryExpandToken function doesn't validate the exchange response shape. If the exchange endpoint returns valid JSON that lacks a token property, json.token is undefined, which is silently returned without throwing. The caller at line 92 then overwrites the valid uploadConfigToken (from previewSession.token) with undefined. The try/catch only catches thrown errors, not undefined returns, so the fallback doesn't engage.

The old code used UploadToken.parse(json) (Zod validation at packages/playground-preview-worker/src/realish.ts:31-36 before this PR) which would throw a ZodError if token was missing, correctly triggering the error path. The new code removes this validation.

Example of the unsafe cast
async function tryExpandToken(exchangeUrl: string): Promise<string> {
  const response = await fetch(exchangeUrl);
  const json = (await response.json()) as { token: string }; // unsafe cast
  return json.token; // undefined if token missing, but typed as string
}
Suggested change
async function tryExpandToken(exchangeUrl: string): Promise<string> {
const response = await fetch(exchangeUrl);
const json = (await response.json()) as { token: string };
return json.token;
}
async function tryExpandToken(exchangeUrl: string): Promise<string> {
const response = await fetch(exchangeUrl);
if (!response.ok) {
throw new Error(`Exchange failed with status ${response.status}`);
}
const json = (await response.json()) as { token?: string };
if (typeof json.token !== "string") {
throw new Error("Exchange response missing token");
}
return json.token;
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +146 to +149
const { token } = parseJSON(bodyText) as {
token: string;
};
return token;
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Wrangler tryExpandToken can silently return undefined, overwriting the valid initial preview session token

In packages/wrangler/src/dev/create-worker-preview.ts, tryExpandToken uses parseJSON(bodyText) as { token: string } which is an unsafe type assertion. If the exchange response is valid JSON but doesn't contain a token field, token is undefined and is returned without throwing. The caller at lines 174-175 then sets previewSessionToken = undefined, overwriting the valid initial token from the edge-preview API. Since the function returned without throwing, the catch block (line 180) doesn't engage, and createPreviewSession proceeds with value: undefined in the returned session object.

Suggested change
const { token } = parseJSON(bodyText) as {
token: string;
};
return token;
const { token } = parseJSON(bodyText) as {
token: string;
};
if (typeof token !== "string") {
throw new Error("Exchange response missing token");
}
return token;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants