Repository navigation
Description
Description
Running opencode web inside a container (Docker/Podman) without a desktop environment prints an ugly ENOENT error because xdg-open is not present in the image. The full error:
662 | var file = this.spawnfile = options.file, spawnargs;
663 | if (options.args === @undefined)
664 | spawnargs = this.spawnargs = [];
665 | else
666 | validateArray(options.args, "options.args"), spawnargs = this.spawnargs = options.args;
667 | if (this.#handle = Bun.spawn({
Error: Executable not found in $PATH: "xdg-open"
path: "xdg-open",
errno: -2,
syscall: "spawn xdg-open",
code: "ENOENT"
at spawn (node:child_process:667:35)
at spawn (node:child_process:14:39)
at <anonymous> (/$bunfs/root/chunk-8mgmcy90.js:3:5152)
Update (1.17.3)
Since 1.17.3 the HTTP server does start even when xdg-open is missing — the crash in 1.17.1 was fixed on 1.17.3. However, the error is still printed to stderr on startup, which is noisy and confusing for users running in containers.
The error has no useful meaning in a container environment: there is no desktop session, no $DISPLAY, and no browser to open. The server works fine, but the user sees a scary stack trace before any output.
Root cause
When opencode web starts, it spawns xdg-open to open the user's default browser. In a container environment (no desktop session, no $DISPLAY, no xdg-utils installed), this call fails with ENOENT. In 1.17.1 the exception killed the entire process. In 1.17.3 the crash is fixed, but the error is still logged to stderr before the server starts.
The server and the browser-opening logic should be decoupled: a missing or failed browser launch should be silent (or at most a warning) in headless environments.
Why this matters
1. Containers are a first-class deployment target
OpenCode's own install page documents docker run -it --rm ghcr.io/anomalyco/opencode as an official installation method. Containers are also used for:
- CI/CD pipelines — running opencode in automated workflows
- Remote development — SSH into a host that runs opencode in a container, access the web UI from the browser on the local machine
- Multi-tenant setups — isolating projects in separate containers on the same host
- Development environments — using Dev Containers, Podman, or Docker Compose for reproducible workspaces
In all these scenarios, the browser runs on the host, not inside the container. The container should only serve the web UI; the user opens it manually from their host browser.
2. The web server and the browser launch are separate concerns
opencode web serves an HTTP interface. Opening a browser is a convenience feature for local desktop usage, but it should not be a hard requirement for the server to start. In headless or containerized contexts, this convenience becomes a blocker.
3. No graceful fallback exists
There is currently no flag like --no-open or --no-browser to skip the browser launch. The environment variable BROWSER is not checked. The only workaround is either:
- Installing
xdg-utilsinside the container (pointless — there is no browser to open) - Creating a fake
/usr/local/bin/xdg-openno-op script (as done inContainerfilein this directory)
Neither is a proper solution.
Suggested fix
The browser launch error should be suppressed or downgraded to a debug-level log when xdg-open is not found:
try {
spawn('xdg-open', [url]);
} catch (err) {
// Container/headless environment — not an error
}Additionally, consider adding:
- A
--no-open/--no-browserCLI flag foropencode web - Support for the
BROWSERenvironment variable as an alternative toxdg-open - A
server.openBrowserconfig option inopencode.json(default:true)
Workaround (immediate)
When you create a custom container and add the standard installation method:
Add this to your Containerfile before the ENTRYPOINT:
# Create a fake xdg-open
RUN printf '#!/bin/sh\nexit 0\n' > /usr/local/bin/xdg-open && chmod +x /usr/local/bin/xdg-openThis creates a no-op xdg-open that satisfies the executable check without actually trying to open a browser.
Environment
- OpenCode version: 1.17.1+ (error persists in 1.17.3, though the crash was fixed)
- OS: Any Linux container (Debian, Alpine, etc.)
- Runtime: Docker, Podman, or any OCI-compatible container runtime
Plugins
standard opencode plugin as it comes from the factory
OpenCode version
1.17.3 but from 1.17.1 exist the issue
Steps to reproduce
Simply pull the standard image: podman pull ghcr.io/anomalyco/opencode:1.17.3 and run with the web parameter and the error will be displayed on the screen.
But this is my Containerfile
FROM docker.io/debian:stable-backports
ENV DEBIAN_FRONTEND=noninteractive
ENV PIP_USER=true
# Pre-configurar tshark para que no requiera setuid (ejecutamos como root en el contenedor)
RUN echo "wireshark-common wireshark-common/install-setuid boolean false" | debconf-set-selections
# Instalar todas las herramientas: esenciales, build, python, red y utilidades varias
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
jq \
bash \
file \
unzip \
zip \
gcc \
g++ \
make \
&& rm -rf /var/lib/apt/lists/*
# Crear la estructura de directorios usada por opencode.sh para XDG y PYTHONUSERBASE
RUN mkdir -p /home/opencode/.config \
/home/opencode/.local/state \
/home/opencode/.local/share \
/home/opencode/.pythonuser
# Instalar opencode mediante el instalador oficial
RUN curl -fsSL https://opencode.ai/install | bash \
&& ln -sf /root/.opencode/bin/opencode /usr/local/bin/opencode
# Copiar y preparar el entrypoint que decide entre modo web y TUI
COPY entrypoint.sh /usr/local/bin/opencode-entrypoint.sh
RUN chmod +x /usr/local/bin/opencode-entrypoint.sh
# Base para paquetes Python instalados por el usuario (persistente mediante volumen)
ENV PYTHONUSERBASE=/home/opencode/.pythonuser
# Directorio de trabajo por defecto: coincide con el volumen montado en opencode.sh
WORKDIR /workspace
# Puerto por defecto del servidor web de opencode
EXPOSE 4096
# Por defecto se arranca el servidor web en 0.0.0.0:4096.
# El modo TUI se activa con OPENCODE_MODE=tui (lo hace opencode.sh con --tui/--no-web).
ENTRYPOINT ["/usr/local/bin/opencode-entrypoint.sh"]
CMD ["web", "--port", "4096", "--hostname", "0.0.0.0"]Entrypoint.sh
#!/bin/bash
set -e
# opencode container entrypoint.
# El modo se selecciona mediante la variable de entorno OPENCODE_MODE:
# - web (por defecto): ejecuta `opencode` con los argumentos recibidos
# (típicamente los del CMD: web --port N --hostname ...)
# - tui: ejecuta `opencode` sin argumentos (arranca la TUI)
MODE="${OPENCODE_MODE:-web}"
case "$MODE" in
tui)
exec opencode
;;
web)
exec opencode "$@"
;;
*)
echo "[ERROR] OPENCODE_MODE desconocido: '$MODE' (valores válidos: 'web', 'tui')" >&2
exit 1
;;
esacScreenshot and/or share link
▄
█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
█ █ █ █ █▀▀▀ █ █ █ █ █ █ █ █▀▀▀
▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀
Local access: http://localhost:4097
Network access: http://192.168.0.113:4097
662 | var file = this.spawnfile = options.file, spawnargs;
663 | if (options.args === @undefined)
664 | spawnargs = this.spawnargs = [];
665 | else
666 | validateArray(options.args, "options.args"), spawnargs = this.spawnargs = options.args;
667 | if (this.#handle = Bun.spawn({
^
error: Executable not found in $PATH: "xdg-open"
path: "xdg-open",
errno: -2,
syscall: "spawn xdg-open",
spawnargs: [ "http://localhost:4097" ],
code: "ENOENT"
at spawn (node:child_process:667:35)
at spawn (node:child_process:14:39)
at <anonymous> (/$bunfs/root/chunk-j1dejnx8.js:3:5152)
Operating System
Host Debian 13 , podman 5.4.2, inside the container: docker.io/debian:stable-backports
Terminal
terminator but this not matter
Activity
kagura-chen commentedon Jun 11, 2026
I'd like to work on this. The fix is straightforward — check if
xdg-open(or equivalent) is available before callingopen()inweb.ts, so the ENOENT error is never spawned in container/headless environments.disarticulate commentedon Jun 13, 2026
same here; unwanted action that should have been a flag. who runs this on baremetal?
garbelini commentedon Jun 15, 2026
quick workaround: add a dummy
xdg-openRUN touch /usr/local/bin/xdg-open; chmod +x /usr/local/bin/xdg-openZipiDan commentedon Jun 19, 2026
Heh I think 90% of the user, hahaha.... My God, 90% irresponsibility.
I don't consider any other option than using podman or similar alternative
fix(cli): check for browser opener before spawning to avoid ENOENT in…
github-actions commentedon Aug 19, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.
ef-anrainer commentedon Aug 19, 2026
Still using the workaround from @garbelini - so what's wrong with the fix PR?
kagura-chen commentedon Aug 19, 2026
Quick note on the current state: the
opencode webhandler inweb.tsalready wraps its browser-open calls with.catch(() => {})(present since commit 1409a07, May 2026), so the ENOENT no longer crashesopencode webin container/headless environments — the issue was auto-closed for inactivity rather than because it was ignored. PR #36875 by @tarcisiomiranda is the open fix awaiting maintainer review; I had also offered a fix back in June but there is no remaining gap that needs a second PR. Thanks for checking in!