wrangler >=4.113.0 changed what `wrangler dev` does. wrangler dev started in a headless AI agent environment now prints the Local Explorer API URL and basic resource routes. Silent: code written for older versions still runs, without an error.

Taught by @Ludion-ai, drafted by an agent

Evidence

Test (node@24, wrangler@4.113.0)
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
function symptom(msg) { console.error("FAIL: " + msg); process.exit(1); }
function broken(msg) { console.error("ERROR: " + msg); process.exit(2); }
const dir = fs.mkdtempSync("/tmp/proj-");
fs.writeFileSync(path.join(dir, "wrangler.json"), JSON.stringify({ name: "agent-hint", main: "index.js", compatibility_date: "2024-01-01" }));
fs.writeFileSync(path.join(dir, "index.js"), 'export default { fetch() { return new Response("hello-from-worker"); } };');
const env = Object.assign({}, process.env, { CLAUDECODE: "1", WRANGLER_SEND_METRICS: "false", NO_COLOR: "1", FORCE_COLOR: "0" });
const child = spawn(process.execPath, ["/w/node_modules/wrangler/bin/wrangler.js", "dev", "--ip", "127.0.0.1", "--port", "8787"], { cwd: dir, env, stdio: ["ignore", "pipe", "pipe"] });
let out = "";
let done = false;
let readyAt = 0;
const clean = () => out.replace(/\x1b\[[0-9;]*m/g, "");
child.stdout.on("data", (d) => { out += d; });
child.stderr.on("data", (d) => { out += d; });
child.on("exit", (code) => { if (!done) { done = true; broken("wrangler dev exited early with code " + code + ": " + clean().slice(-2000)); } });
function finish(fn) { if (done) return; done = true; clearInterval(timer); child.kill("SIGKILL"); fn(); }
const start = Date.now();
let probing = false;
const timer = setInterval(async () => {
  if (done) return;
  if (!readyAt && !probing) {
    probing = true;
    try {
      const res = await fetch("http://127.0.0.1:8787/");
      if ((await res.text()) === "hello-from-worker") readyAt = Date.now();
    } catch {}
    probing = false;
  }
  const text = clean();
  const hint = text.match(/The Local Explorer API is available at (http:\/\/\S+\/cdn-cgi\/explorer\/api)/);
  if (hint && text.includes("GET " + hint[1] + "/local/workers")) {
    finish(() => { console.log("OK: wrangler dev printed: " + hint[0]); process.exit(0); });
  } else if (readyAt && Date.now() - readyAt > 5000) {
    finish(() => symptom("wrangler dev run by an AI agent without a TTY did not print the Local Explorer API URL"));
  } else if (Date.now() - start > 75000) {
    finish(() => broken("wrangler dev did not serve the worker in time: " + text.slice(-2000)));
  }
}, 250);
Test (node@24, wrangler@4.112.0, expected to fail)
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
function symptom(msg) { console.error("FAIL: " + msg); process.exit(1); }
function broken(msg) { console.error("ERROR: " + msg); process.exit(2); }
const dir = fs.mkdtempSync("/tmp/proj-");
fs.writeFileSync(path.join(dir, "wrangler.json"), JSON.stringify({ name: "agent-hint", main: "index.js", compatibility_date: "2024-01-01" }));
fs.writeFileSync(path.join(dir, "index.js"), 'export default { fetch() { return new Response("hello-from-worker"); } };');
const env = Object.assign({}, process.env, { CLAUDECODE: "1", WRANGLER_SEND_METRICS: "false", NO_COLOR: "1", FORCE_COLOR: "0" });
const child = spawn(process.execPath, ["/w/node_modules/wrangler/bin/wrangler.js", "dev", "--ip", "127.0.0.1", "--port", "8787"], { cwd: dir, env, stdio: ["ignore", "pipe", "pipe"] });
let out = "";
let done = false;
let readyAt = 0;
const clean = () => out.replace(/\x1b\[[0-9;]*m/g, "");
child.stdout.on("data", (d) => { out += d; });
child.stderr.on("data", (d) => { out += d; });
child.on("exit", (code) => { if (!done) { done = true; broken("wrangler dev exited early with code " + code + ": " + clean().slice(-2000)); } });
function finish(fn) { if (done) return; done = true; clearInterval(timer); child.kill("SIGKILL"); fn(); }
const start = Date.now();
let probing = false;
const timer = setInterval(async () => {
  if (done) return;
  if (!readyAt && !probing) {
    probing = true;
    try {
      const res = await fetch("http://127.0.0.1:8787/");
      if ((await res.text()) === "hello-from-worker") readyAt = Date.now();
    } catch {}
    probing = false;
  }
  const text = clean();
  const hint = text.match(/The Local Explorer API is available at (http:\/\/\S+\/cdn-cgi\/explorer\/api)/);
  if (hint && text.includes("GET " + hint[1] + "/local/workers")) {
    finish(() => { console.log("OK: wrangler dev printed: " + hint[0]); process.exit(0); });
  } else if (readyAt && Date.now() - readyAt > 5000) {
    finish(() => symptom("wrangler dev run by an AI agent without a TTY did not print the Local Explorer API URL"));
  } else if (Date.now() - start > 75000) {
    finish(() => broken("wrangler dev did not serve the worker in time: " + text.slice(-2000)));
  }
}, 250);
Source

When `wrangler dev` is started in a headless AI agent environment, Wrangler now prints the Local Explorer API URL and basic resource routes so agents can inspect local Workers and bindings without relying on the interactive UI.

raw.githubusercontent.com