Platform behaviour — what the default opener actually does¶
Once a URL clears the gate, the default opener hands it to
github.com/cli/browser, which picks a
mechanism per operating system. What it picks changes what "opening a URL"
costs, what it can fail with, and what appears in your process's output.
This page describes cli/browser v1.3.0, the version pinned in go.mod. If
that pin moves, re-check this page against the release.
What runs on each operating system¶
| GOOS | Mechanism |
|---|---|
darwin |
open <url> |
linux |
The first of xdg-open, x-www-browser, www-browser, wslview found on PATH, in that order |
windows |
The ShellExecute Windows API call — not a subprocess |
freebsd, openbsd |
xdg-open <url>, with install xdg-utils from ports(8) appended if it is missing |
netbsd |
xdg-open <url>, with install xdg-utils from pkgsrc(7) suggested if it is missing |
| anything else | Fails immediately with openBrowser: unsupported operating system: <goos> |
On Linux the wslview entry is what makes this work inside WSL: it hands the URL
to the Windows host's default browser when no native Linux opener is installed.
Is a shell involved?¶
No. On the Unix-like platforms the URL is passed as a single argv element to
exec.Command, so shell metacharacters in the URL — ;, |, backticks, $() —
are inert. There is no sh -c anywhere in the path.
On Windows there is no command line at all: the URL goes to ShellExecute as a
UTF-16 string. Note that this contradicts a common description of Windows URL
opening as a rundll32 url.dll,FileProtocolHandler invocation — that is not what
this dependency does at v1.3.0.
Does OpenURL block until the browser closes?¶
It blocks until the launcher exits, not until the browser closes.
cli/browser uses cmd.Run(), which waits for the spawned process to finish.
In practice xdg-open and open hand the URL to an already-running browser and
return within a few hundred milliseconds. The cases where that is not true are
worth knowing:
- If no browser is running, the launcher may wait for the browser to start.
- Some
xdg-openimplementations do not return until the handler they invoked returns. - On Windows,
ShellExecutereturns as soon as the shell has accepted the request.
Treat OpenURL as a call that usually returns quickly but has no guaranteed
upper bound. If you are on a hot path, call it from a goroutine. The context you
pass will not help: it is checked before the open and has no effect once the
opener is running.
Where does the launcher's output go?¶
To your process's standard output and standard error. cli/browser sets the
spawned command's Stdout and Stderr to package-level variables that default
to os.Stdout and os.Stderr.
This matters for a CLI that writes machine-readable output: a chatty xdg-open
can interleave a warning into your JSON. The variables live in cli/browser, not
in this module, and this module does not expose a way to redirect them. If you
need the output silenced, supply your own Opener
that runs the launcher with the streams you want.
On Windows, ShellExecute produces no such output.
What happens on a headless machine or in CI?¶
The URL passes validation, the opener runs, and it fails. On Linux with none of the four providers installed you get:
invoking URL opener: exec: "xdg-open,x-www-browser,www-browser,wslview": executable file not found in $PATH
A container with xdg-utils installed but no display is worse than one without:
the launcher exists, runs, and fails or hangs depending on the implementation.
For tests, do not rely on either behaviour — inject a fake opener instead, so nothing platform-specific runs at all. See Test without launching a browser.
Which browser gets used?¶
Whichever the operating system has registered as the default handler for the
scheme. Neither this module nor cli/browser chooses a browser, reads $BROWSER
directly, or offers a way to request one. xdg-open consults the desktop's own
configuration, which on many systems does honour $BROWSER; that behaviour is
the launcher's, not this module's, and it varies by distribution.
The same applies to mailto: — the mail client that opens is the OS default, and
there is no way to target a different one from here.
Which platforms are actually tested?¶
None of them, in this repository. The unit tests never invoke the default opener: every test injects a fake, so the suite proves the validation gate and the option plumbing, not the platform handlers.
That means the per-OS behaviour on this page comes from the dependency's source,
not from a test run in this repository. It is accurate for v1.3.0 and it is not
regression-guarded here — a cli/browser bump could change it without failing a
test.
See also¶
- Errors — how an opener failure is wrapped.
- API reference — the
Openerseam for replacing all of this. - What
browserdoes not do — the boundary between this module's guarantees and the platform's behaviour.