browsh-org/browsh · critical

Failed to connect to Firefox's Marionette within 30 seconds

Error message

Failed to connect to Firefox's Marionette within 30 seconds

What it means

Browsh controls Firefox over its Marionette/WebDriver port. `firefoxMarionette` retries dialing that port in a loop for up to 30 seconds; if no connection can be established by then, it concludes Firefox never exposed Marionette and calls `Shutdown`. This means Firefox either didn't start, started without `-marionette`, or is unreachable at the configured host/port.

Source

Thrown at interfacer/src/browsh/firefox.go:230

	connected := false
	slog.Info("Attempting to connect to Firefox Marionette")
	start := time.Now()
	for time.Since(start) < 30*time.Second {
		conn, err = net.Dial("tcp", "127.0.0.1:2828")
		if err != nil {
			if !strings.Contains(err.Error(), "refused") {
				Shutdown(err)
			} else {
				time.Sleep(10 * time.Millisecond)
				continue
			}
		} else {
			connected = true
			break
		}
	}
	if !connected {
		Shutdown(errors.New("Failed to connect to Firefox's Marionette within 30 seconds"))
	}
	marionette = conn
	go readMarionette()
	sendFirefoxCommand("WebDriver:NewSession", map[string]interface{}{})
}

func installWebextension() {
	data, err := browshXpi.ReadFile("browsh.xpi")
	if err != nil {
		Shutdown(err)
	}
	path := path.Join(os.TempDir(), "browsh-webext-addon")
	if err := os.WriteFile(path, []byte(data), 0644); err != nil {
		Shutdown(err)
	}
	args := map[string]interface{}{"path": path}
	sendFirefoxCommand("Addon:Install", args)
}

View on GitHub (pinned to 499ef386d4)

Solutions

  1. Verify Firefox starts manually with Marionette: run the same command with `-marionette` and check it doesn't crash.
  2. Confirm the Marionette port (default 2828) is free and reachable: `ss -tlnp | grep 2828`.
  3. Retry on a faster/less loaded machine or warm up Firefox before timing-sensitive runs; check for missing shared libraries (`ldd $(which firefox)`).
  4. Inspect the launched Firefox's stderr/logs for immediate crash causes (profile lock, missing libraries, no display).

Example fix

// before (shell check)
firefox --headless  # starts without marionette -> 30s timeout
// after
firefox --headless --marionette  # marionette listens on :2828
ss -tlnp | grep 2828  # confirm listening before running browsh
Defensive patterns

Strategy: retry

Validate before calling

const net = require('net');
const s = net.connect(2828, '127.0.0.1');
s.on('connect', () => { console.log('marionette up'); s.end(); });
s.on('error', () => console.error('marionette not listening; firefox may have failed to start'));

Try / catch

try {
  startBrowsh();
} catch (e) {
  if (/Marionette within 30 seconds/.test(e.message)) {
    // warm-up retry: give slow machines a chance
    setTimeout(startBrowsh, 5000);
  } else { throw e; }
}

Prevention

When it happens

Trigger: `setupFirefox` or `StartFirefox` calls `firefoxMarionette`; the dial loop never succeeds within 30s — Firefox failed to launch (bad binary/flags), crashed immediately on startup, started too slowly (cold start under load), or Marionette is disabled/bound elsewhere.

Common situations: Slow/loaded CI machines where Firefox takes >30s to start; Firefox crashing on launch due to missing X/GTK libs or profile issues; running a Firefox build with Marionette disabled; resource-starved containers hitting startup timeouts.

Related errors


AI-assisted analysis of browsh-org/browsh@499ef386d4 (2026-09-02). Data as JSON: /api/errors/bde33158a340d668. Report an issue: GitHub.