fix(codex): expose app-server env controls

This commit is contained in:
pashpashpash
2026-04-27 18:27:30 -04:00
committed by Peter Steinberger
parent 09c39463bb
commit aeb007e4e5
5 changed files with 82 additions and 14 deletions

View File

@@ -109,6 +109,10 @@
"type": "object",
"additionalProperties": { "type": "string" }
},
"clearEnv": {
"type": "array",
"items": { "type": "string" }
},
"requestTimeoutMs": {
"type": "number",
"minimum": 1,
@@ -234,6 +238,11 @@
"help": "Additional headers sent to the WebSocket app-server.",
"advanced": true
},
"appServer.clearEnv": {
"label": "Clear Environment",
"help": "Environment variable names removed from the spawned stdio app-server process after overrides are applied.",
"advanced": true
},
"appServer.requestTimeoutMs": {
"label": "Request Timeout",
"help": "Maximum time to wait for Codex app-server control-plane requests.",

View File

@@ -18,6 +18,7 @@ describe("Codex app-server config", () => {
transport: "websocket",
url: "ws://127.0.0.1:39175",
headers: { "X-Test": "yes" },
clearEnv: ["OPENAI_API_KEY"],
approvalPolicy: "on-request",
sandbox: "danger-full-access",
approvalsReviewer: "guardian_subagent",
@@ -40,11 +41,29 @@ describe("Codex app-server config", () => {
transport: "websocket",
url: "ws://127.0.0.1:39175",
headers: { "X-Test": "yes" },
clearEnv: ["OPENAI_API_KEY"],
}),
}),
);
});
it("normalizes app-server environment variables to clear", () => {
const runtime = resolveCodexAppServerRuntimeOptions({
pluginConfig: {
appServer: {
clearEnv: [" OPENAI_API_KEY ", "", " "],
},
},
env: {},
});
expect(runtime.start).toEqual(
expect.objectContaining({
clearEnv: ["OPENAI_API_KEY"],
}),
);
});
it("drops invalid legacy service tiers without discarding the rest of the config", () => {
const runtime = resolveCodexAppServerRuntimeOptions({
pluginConfig: {

View File

@@ -66,6 +66,7 @@ export type CodexPluginConfig = {
url?: string;
authToken?: string;
headers?: Record<string, string>;
clearEnv?: string[];
requestTimeoutMs?: number;
approvalPolicy?: CodexAppServerApprovalPolicy;
sandbox?: CodexAppServerSandboxMode;
@@ -83,6 +84,7 @@ export const CODEX_APP_SERVER_CONFIG_KEYS = [
"url",
"authToken",
"headers",
"clearEnv",
"requestTimeoutMs",
"approvalPolicy",
"sandbox",
@@ -152,6 +154,7 @@ const codexPluginConfigSchema = z
url: z.string().optional(),
authToken: z.string().optional(),
headers: z.record(z.string(), z.string()).optional(),
clearEnv: z.array(z.string()).optional(),
requestTimeoutMs: z.number().positive().optional(),
approvalPolicy: codexAppServerApprovalPolicySchema.optional(),
sandbox: codexAppServerSandboxSchema.optional(),
@@ -188,6 +191,7 @@ export function resolveCodexAppServerRuntimeOptions(
: "managed";
const args = resolveArgs(config.args, env.OPENCLAW_CODEX_APP_SERVER_ARGS);
const headers = normalizeHeaders(config.headers);
const clearEnv = normalizeStringList(config.clearEnv);
const authToken = readNonEmptyString(config.authToken);
const url = readNonEmptyString(config.url);
const policyMode =
@@ -210,6 +214,7 @@ export function resolveCodexAppServerRuntimeOptions(
...(url ? { url } : {}),
...(authToken ? { authToken } : {}),
headers,
...(clearEnv.length > 0 ? { clearEnv } : {}),
},
requestTimeoutMs: normalizePositiveNumber(config.requestTimeoutMs, 60_000),
approvalPolicy:
@@ -373,6 +378,15 @@ function normalizeHeaders(value: unknown): Record<string, string> {
);
}
function normalizeStringList(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
return value
.map((entry) => readNonEmptyString(entry))
.filter((entry): entry is string => entry !== undefined);
}
function readBooleanEnv(value: string | undefined): boolean | undefined {
if (value === undefined) {
return undefined;