From ef49835c4f57dd3f64e6b3f6fc15a007233b4a09 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Mon, 10 Aug 2026 20:47:26 +0200 Subject: [PATCH] CB-528: the CodexHome seam, ahead of the adapter that uses it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex reads everything from CODEX_HOME — config, credentials, sessions, skills, plugins, state. Pointing a peer at the operator's own ~/.codex would hand it the operator's tool surface and let it write into the operator's session history: the same failure CB-525 exists to prevent on the Claude side, in a runtime where there is no --mcp-config to neutralize. Extracted as an interface rather than a launcher method because provisioning is filesystem work with its own failure modes. The common one is a missing credential, which Codex surfaces as an opaque 401 mid-turn instead of a spawn error — so the contract says provision() must fail loudly there. Splitting it also lets the launcher be tested without touching a real home directory. Lands before the adapter so the launcher and the provisioner can be built against a fixed seam instead of against each other. --- .../ltms/bridged/config/BridgedConfig.java | 6 +++ .../dev/ltms/bridged/worker/CodexHome.java | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 bridged/src/main/java/dev/ltms/bridged/worker/CodexHome.java diff --git a/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java b/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java index c1816df..3f3b8d9 100644 --- a/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java +++ b/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java @@ -126,6 +126,12 @@ public record BridgedConfig( public static final String KIND_CLAUDE_CODE = "claude-code"; /** Peer kind spawned by the opencode adapter (CB-402). */ public static final String KIND_OPENCODE = "opencode"; + /** + * Peer kind spawned by the Codex adapter (CB-528). Like {@link #KIND_OPENCODE} it carries + * its own argv and never inherits the Claude binary, and it sits outside the + * {@code ANTHROPIC_BASE_URL} subscription guard because Codex has no such seam. + */ + public static final String KIND_CODEX = "codex"; public Worker { // A claude-code worker defaults its launch command to `claude`; other kinds carry their own diff --git a/bridged/src/main/java/dev/ltms/bridged/worker/CodexHome.java b/bridged/src/main/java/dev/ltms/bridged/worker/CodexHome.java new file mode 100644 index 0000000..52cba32 --- /dev/null +++ b/bridged/src/main/java/dev/ltms/bridged/worker/CodexHome.java @@ -0,0 +1,48 @@ +package dev.ltms.bridged.worker; + +import dev.ltms.bridged.config.BridgedConfig; + +import java.nio.file.Path; + +/** + * Provisions an isolated {@code CODEX_HOME} for one Codex peer (CB-528). + * + *

Codex reads everything from {@code CODEX_HOME} — its config, credentials, sessions, + * skills, plugins, and state. Pointing a peer at the operator's own {@code ~/.codex} would give it + * the operator's tool surface and let it write into the operator's session history, which is the + * same class of failure CB-525 exists to prevent on the Claude side. So every peer gets its own + * directory, and this is the seam that builds it. + * + *

It is an interface rather than a method on the launcher for two reasons: provisioning is + * filesystem work with its own failure modes (a missing credential is the most common, and it + * surfaces as an opaque {@code 401} from Codex rather than a spawn error), and keeping it separate + * lets the launcher be tested without touching a real home directory. + * + *

Three things the implementation must put in the home, because Codex has no launch flag for + * any of them: + *

+ */ +public interface CodexHome { + + /** + * Build a fresh, isolated home for a peer launching under {@code cfg} and return its path, + * suitable for the {@code CODEX_HOME} environment variable. + * + * @param cfg the profile being launched; supplies the MCP URL and any bearer-token variable + * @return the provisioned directory + * @throws RuntimeException if the home cannot be provisioned — including when no credential is + * available, which must fail loudly here rather than as a 401 later + */ + Path provision(BridgedConfig.Worker cfg); + + /** + * Remove a home previously returned by {@link #provision}. Idempotent: releasing an already + * released or never provisioned path is not an error, because teardown races teardown. + */ + void release(Path home); +}