Skip to content

Commit 7ffbd03

Browse files
committed
fix(codex): preserve user home for app-server launches
1 parent b10b946 commit 7ffbd03

4 files changed

Lines changed: 39 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai
1010

1111
### Fixes
1212

13+
- Codex app-server: keep per-agent `CODEX_HOME` isolation without rewriting `HOME` by default, so Codex-run subprocesses can still find normal user-home config, tokens, and CLI state unless the launch explicitly overrides `HOME`.
1314
- ACP: preserve redacted numeric JSON-RPC `RequestError` details in runtime failure text, so backend diagnostics are visible instead of only `Internal error`. Fixes #81126. (#81188) Thanks @vyctorbrzezowski.
1415
- Security/Windows ACL audit: classify Anonymous Logon, Guests, Interactive, Local, and Network SIDs as world-equivalent principals so broadly writable paths stay critical instead of being downgraded to group-writable. Fixes #74350. (#74383) Thanks @dwc1997.
1516
- Media-understanding: retry transient remote attachment fetch failures before audio or vision processing, so Discord voice notes are not lost after one network/CDN blip. Fixes #74316. Thanks @vyctorbrzezowski and @gabrielexito-stack.

docs/cli/migrate.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ inventory a specific Codex home.
123123

124124
Use this provider when moving to the OpenClaw Codex harness and you want to
125125
promote useful personal Codex CLI assets deliberately. Local Codex app-server
126-
launches use per-agent `CODEX_HOME` and `HOME` directories, so they do not read
127-
your personal Codex CLI state by default.
126+
launches use a per-agent `CODEX_HOME`, so they do not read your personal Codex
127+
CLI state by default, while subprocesses still inherit the normal process
128+
`HOME` unless the app-server launch explicitly overrides it.
128129

129130
Running `openclaw migrate codex` in an interactive terminal previews the full
130131
plan, then opens checkbox selectors before the final apply confirmation. Skill

extensions/codex/src/app-server/auth-bridge.test.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ async function writeCodexCliAuthFile(codexHome: string): Promise<void> {
172172
}
173173

174174
describe("bridgeCodexAppServerStartOptions", () => {
175-
it("sets agent-owned CODEX_HOME and HOME for local app-server launches", async () => {
175+
it("sets agent-owned CODEX_HOME without overriding HOME for local app-server launches", async () => {
176176
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
177177
const startOptions = createStartOptions();
178178
try {
@@ -188,17 +188,40 @@ describe("bridgeCodexAppServerStartOptions", () => {
188188
...startOptions,
189189
env: {
190190
CODEX_HOME: codexHome,
191-
HOME: nativeHome,
192191
},
193192
});
194193
await expect(fs.access(codexHome)).resolves.toBeUndefined();
195-
await expect(fs.access(nativeHome)).resolves.toBeUndefined();
194+
await expectPathMissing(nativeHome);
196195
expect(startOptions.env).toBeUndefined();
197196
} finally {
198197
await fs.rm(agentDir, { recursive: true, force: true });
199198
}
200199
});
201200

201+
it("preserves inherited HOME when clearEnv asks to clear app-server isolation vars", async () => {
202+
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
203+
const startOptions = createStartOptions({
204+
clearEnv: ["CODEX_HOME", "HOME", "FOO"],
205+
});
206+
try {
207+
await expect(
208+
bridgeCodexAppServerStartOptions({
209+
startOptions,
210+
agentDir,
211+
}),
212+
).resolves.toEqual({
213+
...startOptions,
214+
env: {
215+
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
216+
},
217+
clearEnv: ["FOO"],
218+
});
219+
expect(startOptions.clearEnv).toEqual(["CODEX_HOME", "HOME", "FOO"]);
220+
} finally {
221+
await fs.rm(agentDir, { recursive: true, force: true });
222+
}
223+
});
224+
202225
it("preserves explicit CODEX_HOME and HOME overrides", async () => {
203226
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
204227
const codexHome = path.join(agentDir, "custom-codex-home");
@@ -260,7 +283,6 @@ describe("bridgeCodexAppServerStartOptions", () => {
260283
env: {
261284
EXISTING: "1",
262285
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
263-
HOME: resolveCodexAppServerNativeHomeDir(agentDir),
264286
},
265287
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
266288
});
@@ -298,7 +320,6 @@ describe("bridgeCodexAppServerStartOptions", () => {
298320
...startOptions,
299321
env: {
300322
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
301-
HOME: resolveCodexAppServerNativeHomeDir(agentDir),
302323
},
303324
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
304325
});
@@ -331,7 +352,6 @@ describe("bridgeCodexAppServerStartOptions", () => {
331352
...startOptions,
332353
env: {
333354
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
334-
HOME: resolveCodexAppServerNativeHomeDir(agentDir),
335355
},
336356
clearEnv: ["FOO", "CODEX_API_KEY", "OPENAI_API_KEY"],
337357
});
@@ -364,7 +384,6 @@ describe("bridgeCodexAppServerStartOptions", () => {
364384
...startOptions,
365385
env: {
366386
CODEX_HOME: resolveCodexAppServerHomeDir(agentDir),
367-
HOME: resolveCodexAppServerNativeHomeDir(agentDir),
368387
},
369388
});
370389
} finally {

extensions/codex/src/app-server/auth-bridge.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const CODEX_APP_SERVER_NATIVE_HOME_DIRNAME = "home";
3535
const CODEX_API_KEY_ENV_VAR = "CODEX_API_KEY";
3636
const OPENAI_API_KEY_ENV_VAR = "OPENAI_API_KEY";
3737
const CODEX_APP_SERVER_API_KEY_ENV_VARS = [CODEX_API_KEY_ENV_VAR, OPENAI_API_KEY_ENV_VAR];
38-
const CODEX_APP_SERVER_ISOLATION_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
38+
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
3939

4040
type AuthProfileOrderConfig = Parameters<typeof resolveAuthProfileOrder>[0]["cfg"];
4141

@@ -262,18 +262,20 @@ async function withAgentCodexHomeEnvironment(
262262
: resolveCodexAppServerHomeDir(agentDir);
263263
const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim()
264264
? startOptions.env[HOME_ENV_VAR]
265-
: path.join(codexHome, CODEX_APP_SERVER_NATIVE_HOME_DIRNAME);
265+
: undefined;
266266
await fs.mkdir(codexHome, { recursive: true });
267-
await fs.mkdir(nativeHome, { recursive: true });
267+
if (nativeHome) {
268+
await fs.mkdir(nativeHome, { recursive: true });
269+
}
268270
const nextStartOptions: CodexAppServerStartOptions = {
269271
...startOptions,
270272
env: {
271273
...startOptions.env,
272274
[CODEX_HOME_ENV_VAR]: codexHome,
273-
[HOME_ENV_VAR]: nativeHome,
275+
...(nativeHome ? { [HOME_ENV_VAR]: nativeHome } : {}),
274276
},
275277
};
276-
const clearEnv = withoutClearedCodexIsolationEnv(startOptions.clearEnv);
278+
const clearEnv = withoutClearedCodexHomeEnv(startOptions.clearEnv);
277279
if (clearEnv) {
278280
nextStartOptions.clearEnv = clearEnv;
279281
} else {
@@ -282,11 +284,11 @@ async function withAgentCodexHomeEnvironment(
282284
return nextStartOptions;
283285
}
284286

285-
function withoutClearedCodexIsolationEnv(clearEnv: string[] | undefined): string[] | undefined {
287+
function withoutClearedCodexHomeEnv(clearEnv: string[] | undefined): string[] | undefined {
286288
if (!clearEnv) {
287289
return undefined;
288290
}
289-
const reserved = new Set(CODEX_APP_SERVER_ISOLATION_ENV_VARS);
291+
const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
290292
const filtered = clearEnv.filter((envVar) => !reserved.has(envVar.trim().toUpperCase()));
291293
return filtered.length === clearEnv.length ? clearEnv : filtered;
292294
}

0 commit comments

Comments
 (0)