From de476d5b8fc629163654b2c7f0517f0fe7030b1c Mon Sep 17 00:00:00 2001 From: anessbelbati <102622182+anessbelbati@users.noreply.github.com> Date: Wed, 7 Oct 2026 04:54:28 +0100 Subject: [PATCH] Name the display variables for the tool server Codex starts on Linux Codex starts an MCP server with a fixed short list of environment variables (HOME, PATH, LANG and a few more), not with its own environment. On Linux the launcher in .codex/config.toml is the desktop app itself, and without DISPLAY it exits at startup: Missing X server or $DISPLAY The platform failed to initialize. Exiting. Codex then logs "MCP server startup failed" for video-fs and offers the model none of its tools. Measured with Codex 0.160.1 on ubuntu-latest against a stand-in model server: none of the app's tools in the model request with the entry as it was written before, the video-fs tools in the request with this change. Started by hand with only the variables Codex passes, the launcher ended after 1.2 s without answering; with the display variables added it answered the MCP initialize request. The entry now lists the display variables under env_vars, the key Codex reads for variables to pass through from its own environment. Only an X11 display could be tried on the runner. The Wayland names are there so that a Wayland session can reach its compositor the same way. Claude Code needed no change: on the same runner it offered the video-fs tools already. What is written on macOS and Windows does not change. --- desktop/agent-setup.mjs | 14 ++++++++++++++ desktop/agent-setup.test.mjs | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/desktop/agent-setup.mjs b/desktop/agent-setup.mjs index ed572f5..3384af2 100644 --- a/desktop/agent-setup.mjs +++ b/desktop/agent-setup.mjs @@ -21,6 +21,17 @@ import { validateProjectId } from "./project-binding.mjs"; const GUIDE_NAME = "VIDEO_FS_AGENT_GUIDE.md"; +/** Codex starts an MCP server with a short fixed list of environment variables. + * On Linux the launcher is the desktop app, which exits at startup when it + * cannot reach the session's display, so Codex has to pass these on as well. */ +const CODEX_DISPLAY_ENV_VARS = [ + "DISPLAY", + "WAYLAND_DISPLAY", + "XAUTHORITY", + "XDG_RUNTIME_DIR", + "XDG_SESSION_TYPE", +]; + export function projectIdFromAppUrl(candidate, appUrl) { let url; try { @@ -70,6 +81,9 @@ export async function setupAgentProject({ projectId: rawProjectId, state }) { "[mcp_servers.video-fs]", `command = ${tomlString(launcher.command)}`, `args = ${tomlStringArray(launcher.args)}`, + ...(process.platform === "linux" + ? [`env_vars = ${tomlStringArray(CODEX_DISPLAY_ENV_VARS)}`] + : []), "", ].join("\n"); const claudeHookConfig = hookConfig(claudeHookLauncher); diff --git a/desktop/agent-setup.test.mjs b/desktop/agent-setup.test.mjs index 95bf0b8..f26c0b3 100644 --- a/desktop/agent-setup.test.mjs +++ b/desktop/agent-setup.test.mjs @@ -337,6 +337,15 @@ test("both generated launchers bind exactly the opened project", async () => { ); assert.match(codex, /args = \["--mcp", "--project-id", "bound-project"\]/); assert.doesNotMatch(codex, /project-a|project-b/); + // Codex hands an MCP server none of the display variables unless they are + // named, and on Linux the launcher cannot start without them. + assert.equal( + codex.includes( + 'env_vars = ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY", "XDG_RUNTIME_DIR", "XDG_SESSION_TYPE"]\n', + ), + process.platform === "linux", + ); + assert.equal(codex.includes("env_vars"), process.platform === "linux"); const claudeHooks = JSON.parse( await readFile(