windmill-labs/windmill · warning

Failed to open browser: ${e.message}

Error message

Failed to open browser: ${e.message}

What it means

`wmill pipeline dev` tries to open the pipeline dev URL in your default browser via the `open` package when --open is not false. If launching the browser fails, it logs this warning and continues — the server keeps running and the URL is printed, so you can open it manually.

Source

Thrown at cli/src/commands/pipeline/dev.ts:263

  });

  // The `/pipeline_dev` page is served by the frontend, not the backend. By
  // default we open it on the workspace remote, but that 404s on a remote whose
  // deployed frontend predates this route — `--frontend` points the page at a
  // locally-run frontend (`REMOTE=<remote> npm run dev`) while the API/token
  // still target the remote. Normalize to a single trailing slash either way.
  const pageBase = (opts.frontend ?? workspace.remote).replace(/\/?$/, "/");
  const url =
    `${pageBase}pipeline_dev?workspace=${workspace.workspaceId}` +
    `&wm_token=${workspace.token}&folder=${encodeURIComponent(folder)}&port=${port}` +
    `&ws_token=${encodeURIComponent(wsToken)}`;

  server.listen(port, LISTEN_HOST, () => {
    log.info(colors.green.bold(`🚀 Pipeline dev server on ws://localhost:${port}/ws`));
    log.info(colors.gray(`Open: ${url}`));
    if (opts.open !== false) {
      open.default(url).catch((e: any) =>
        log.warn(colors.yellow(`Failed to open browser: ${e.message}`)),
      );
    }
  });

  process.on("SIGINT", () => {
    log.info(colors.yellow("\n🛑 Shutting down…"));
    watcher.close();
    for (const ws of clients) ws.close();
    server.close();
    process.exit(0);
  });
}

const command = new Command()
  .description(
    "Live-preview a data pipeline from local files: watch an `f/<folder>` of `// pipeline` scripts, push the working-tree graph to the dev page, and run the cascade via preview (no deploy).",
  )
  .arguments("[folder:string]")

View on GitHub (pinned to e474e8803c)

Solutions

  1. Copy the `Open: <url>` line printed by the command into a browser manually — nothing else is broken.
  2. Pass --open=false (or --no-open) to suppress the attempt and the warning in headless environments.
  3. Install/configure a browser opener on the host (xdg-open, or set $BROWSER on Linux).
  4. Set DISPLAY/Wayland session variables if running from a non-GUI shell on a desktop machine.

Example fix

// before (headless box, warning every run)
wmill pipeline dev
// after
wmill pipeline dev --open=false
Defensive patterns

Strategy: fallback

Validate before calling

// detect headless environment before relying on browser opening
const headless = !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY && process.platform === 'linux';
const args = headless ? ['--open=false'] : [];

Try / catch

// the CLI already catches internally; when scripting, tolerate it
open.default(url).catch(e => {
  console.warn(`browser failed (${e.message}); open ${url} manually`);
});

Prevention

When it happens

Trigger: server.listen succeeded and opts.open !== false, but the `open` promise rejects: no browser/xdg-open available, headless CI/SSH/container environment, DISPLAY/WAYLAND not set, or a malformed default-browser handler.

Common situations: Running in WSL without a Windows browser configured; SSH into a server with no GUI; Docker container; CI runner; Linux box without xdg-open; macOS default browser handler broken.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/a64a553930023605. Report an issue: GitHub.