Fix Codex auth handoff for the app-server harness (#69990)

* Codex: fix auth bridge token shape

* Codex: preserve selected auth tokens

* Codex: prefer selected profile id token

* Codex: honor inherited Codex home

---------

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
This commit is contained in:
pashpashpash
2026-04-22 00:22:29 -07:00
committed by GitHub
parent a4cafde0da
commit 1dd3fb1611
4 changed files with 312 additions and 4 deletions

View File

@@ -89,6 +89,7 @@ describe("bridgeCodexAppServerStartOptions", () => {
refresh_token: "refresh-token",
account_id: "acct-123",
},
last_refresh: expect.any(String),
});
if (process.platform !== "win32") {
const authStat = await fs.stat(path.join(result.env?.CODEX_HOME ?? "", "auth.json"));
@@ -96,6 +97,171 @@ describe("bridgeCodexAppServerStartOptions", () => {
}
});
it("hydrates Codex-only auth fields from a matching Codex CLI auth file", async () => {
const sourceCodexHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-source-home-"));
tempDirs.push(sourceCodexHome);
await fs.writeFile(
path.join(sourceCodexHome, "auth.json"),
`${JSON.stringify(
{
auth_mode: "chatgpt",
tokens: {
id_token: "source-id-token",
access_token: "access-token",
refresh_token: "refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
},
null,
2,
)}\n`,
);
const agentDir = await createAgentDirWithDefaultProfile({
accountId: "acct-123",
});
const result = await bridgeCodexAppServerStartOptions({
startOptions: {
transport: "stdio",
command: "codex",
args: ["app-server"],
headers: {},
env: { CODEX_HOME: sourceCodexHome },
},
agentDir,
});
expect(result.env?.CODEX_HOME).not.toBe(sourceCodexHome);
const authFile = JSON.parse(
await fs.readFile(path.join(result.env?.CODEX_HOME ?? "", "auth.json"), "utf8"),
);
expect(authFile).toEqual({
auth_mode: "chatgpt",
tokens: {
id_token: "source-id-token",
access_token: "access-token",
refresh_token: "refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
});
});
it("keeps the selected profile tokens when hydrating from a same-account Codex CLI auth file", async () => {
const sourceCodexHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-source-home-"));
tempDirs.push(sourceCodexHome);
await fs.writeFile(
path.join(sourceCodexHome, "auth.json"),
`${JSON.stringify(
{
auth_mode: "chatgpt",
tokens: {
id_token: "source-id-token",
access_token: "stale-source-access-token",
refresh_token: "stale-source-refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
},
null,
2,
)}\n`,
);
const agentDir = await createAgentDirWithDefaultProfile({
access: "selected-profile-access-token",
refresh: "selected-profile-refresh-token",
accountId: "acct-123",
idToken: "selected-profile-id-token",
});
const result = await bridgeCodexAppServerStartOptions({
startOptions: {
transport: "stdio",
command: "codex",
args: ["app-server"],
headers: {},
env: { CODEX_HOME: sourceCodexHome },
},
agentDir,
});
expect(result.env?.CODEX_HOME).not.toBe(sourceCodexHome);
const authFile = JSON.parse(
await fs.readFile(path.join(result.env?.CODEX_HOME ?? "", "auth.json"), "utf8"),
);
expect(authFile).toEqual({
auth_mode: "chatgpt",
tokens: {
id_token: "selected-profile-id-token",
access_token: "selected-profile-access-token",
refresh_token: "selected-profile-refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
});
});
it("hydrates from inherited CODEX_HOME when start options do not override it", async () => {
const sourceCodexHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-source-home-"));
tempDirs.push(sourceCodexHome);
await fs.writeFile(
path.join(sourceCodexHome, "auth.json"),
`${JSON.stringify(
{
auth_mode: "chatgpt",
tokens: {
id_token: "source-id-token",
access_token: "access-token",
refresh_token: "refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
},
null,
2,
)}\n`,
);
const previousCodexHome = process.env.CODEX_HOME;
process.env.CODEX_HOME = sourceCodexHome;
try {
const agentDir = await createAgentDirWithDefaultProfile({
accountId: "acct-123",
});
const result = await bridgeCodexAppServerStartOptions({
startOptions: {
transport: "stdio",
command: "codex",
args: ["app-server"],
headers: {},
},
agentDir,
});
expect(result.env?.CODEX_HOME).not.toBe(sourceCodexHome);
const authFile = JSON.parse(
await fs.readFile(path.join(result.env?.CODEX_HOME ?? "", "auth.json"), "utf8"),
);
expect(authFile).toEqual({
auth_mode: "chatgpt",
tokens: {
id_token: "source-id-token",
access_token: "access-token",
refresh_token: "refresh-token",
account_id: "acct-123",
},
last_refresh: "2026-04-22T00:00:00.000Z",
});
} finally {
if (previousCodexHome === undefined) {
delete process.env.CODEX_HOME;
} else {
process.env.CODEX_HOME = previousCodexHome;
}
}
});
it("leaves start options unchanged when canonical oauth is unavailable", async () => {
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-app-server-"));
tempDirs.push(agentDir);

View File

@@ -13,6 +13,7 @@ export async function bridgeCodexAppServerStartOptions(params: {
agentDir: params.agentDir,
bridgeDir: "harness-auth",
profileId,
sourceCodexHome: params.startOptions.env?.CODEX_HOME,
});
if (!bridge) {
return params.startOptions;