browsh-org/browsh · error

A headless Firefox is already running

Error message

A headless Firefox is already running

What it means

Browsh's interfacer refuses to start another headless Firefox when it detects an existing one. On non-Windows platforms `checkIfFirefoxIsAlreadyRunning` runs `ps aux` and matches any process line against the regex `firefox.*--headless`; a match means a headless instance (possibly a previous browsh run) is still alive, and starting a second one would conflict over the Web Extension connection and resources. The process calls `Shutdown`, terminating browsh.

Source

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

		Shutdown(err)
	}
	if err := firefoxProcess.Start(); err != nil {
		Shutdown(err)
	}
	in := bufio.NewScanner(stdout)
	for in.Scan() {
		slog.Info("FF-CONSOLE", "stdout", in.Text())
	}
}

func checkIfFirefoxIsAlreadyRunning() {
	if runtime.GOOS == "windows" {
		return
	}
	processes := Shell("ps aux")
	r, _ := regexp.Compile("firefox.*--headless")
	if r.MatchString(processes) {
		Shutdown(errors.New("A headless Firefox is already running"))
	}
}

func ensureFirefoxBinary() string {
	path := viper.GetString("firefox.path")
	if path == "firefox" {
		switch runtime.GOOS {
		case "windows":
			path = getFirefoxPath()
		case "darwin":
			path = "/Applications/Firefox.app/Contents/MacOS/firefox"
		default:
			path = getFirefoxPath()
		}
	}
	if _, err := os.Stat(path); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			err = errors.New("Firefox binary not found: " + path)

View on GitHub (pinned to 499ef386d4)

Solutions

  1. Find and kill the existing headless Firefox: `pkill -f 'firefox.*--headless'`, then retry.
  2. Run `ps aux | grep firefox` to confirm it is safe to kill before killing it.
  3. Avoid running multiple browsh instances simultaneously; serialize browsh invocations in scripts/CI.
  4. If browsh crashed and left orphans, fix cleanup (trap signals, use process groups) so Firefox is terminated with browsh.

Example fix

// before (shell, failing)
browsh http://example.com  # Error: A headless Firefox is already running
// after
pkill -f 'firefox.*--headless' || true
browsh http://example.com
Defensive patterns

Strategy: validation

Validate before calling

const { execSync } = require('child_process');
const running = execSync('ps aux').toString().match(/firefox.*--headless/);
if (running) throw new Error('Kill existing headless Firefox before starting browsh');

Try / catch

try {
  startBrowsh();
} catch (e) {
  if (/headless Firefox is already running/.test(e.message)) {
    execSync("pkill -f 'firefox.*--headless'");
    startBrowsh();
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling StartFirefox (via `startHeadlessFirefox` or `startWERFirefox`) when `ps aux` output contains a line matching `firefox.*--headless` — e.g. a leftover headless Firefox from a crashed or still-running browsh session. Never triggered on Windows (the check returns early).

Common situations: Previous browsh run didn't clean up its Firefox child process; a systemd/CI job left Firefox running; a script that starts browsh in a loop without killing the old instance; running two browsh instances concurrently in the same container.

Related errors


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