Skip to content

Commit d699be5

Browse files
committed
docs(cli): document and test wrapper forwarding
1 parent 2ef3ba1 commit d699be5

3 files changed

Lines changed: 90 additions & 22 deletions

File tree

docs/reference/storage-paths.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Compatibility note:
4242

4343
- This file-store forwarding keeps auth state readable from disk outside interactive terminals, so wrapper forwarding and non-TTY auth flows stay deterministic after the Ink migration.
4444

45+
> **Windows note:** The wrapper keeps the official Codex CLI file-store layout unchanged, so Windows `EPERM`/`EBUSY` retry handling still lives with the downstream CLI writes rather than this wrapper layer. Opting out with `CODEX_MULTI_AUTH_FORCE_FILE_AUTH_STORE=0` stops injecting the file-store override for future wrapper launches, but it does not rewrite or expose previously written CLI auth files beyond the standard `~/.codex/auth.json` and `~/.codex/accounts.json` locations.
46+
4547
---
4648

4749
## Project-Scoped Account Paths

test/codex-bin-wrapper.test.ts

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import { spawn, spawnSync, type SpawnSyncReturns } from "node:child_process";
2-
import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1+
import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
2+
import {
3+
copyFileSync,
4+
mkdirSync,
5+
mkdtempSync,
6+
readFileSync,
7+
rmSync,
8+
writeFileSync,
9+
} from "node:fs";
310
import { tmpdir } from "node:os";
411
import { delimiter, dirname, join } from "node:path";
512
import process from "node:process";
@@ -42,8 +49,14 @@ function createWrapperFixture(): string {
4249
createdDirs.push(fixtureRoot);
4350
const scriptDir = join(fixtureRoot, "scripts");
4451
mkdirSync(scriptDir, { recursive: true });
45-
copyFileSync(join(repoRootDir, "scripts", "codex.js"), join(scriptDir, "codex.js"));
46-
copyFileSync(join(repoRootDir, "scripts", "codex-routing.js"), join(scriptDir, "codex-routing.js"));
52+
copyFileSync(
53+
join(repoRootDir, "scripts", "codex.js"),
54+
join(scriptDir, "codex.js"),
55+
);
56+
copyFileSync(
57+
join(repoRootDir, "scripts", "codex-routing.js"),
58+
join(scriptDir, "codex-routing.js"),
59+
);
4760
return fixtureRoot;
4861
}
4962

@@ -149,7 +162,9 @@ function runWrapperAsync(
149162
});
150163
}
151164

152-
function combinedOutput(result: SpawnSyncReturns<string> | WrapperAsyncResult): string {
165+
function combinedOutput(
166+
result: SpawnSyncReturns<string> | WrapperAsyncResult,
167+
): string {
153168
return `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
154169
}
155170

@@ -185,6 +200,32 @@ describe("codex bin wrapper", () => {
185200
expect(result.stdout).toContain("FORWARDED:--version");
186201
});
187202

203+
it("injects file auth store forwarding for wrapped real cli invocations by default", () => {
204+
const fixtureRoot = createWrapperFixture();
205+
const fakeBin = createFakeCodexBin(fixtureRoot);
206+
const result = runWrapper(fixtureRoot, ["exec", "status"], {
207+
CODEX_MULTI_AUTH_REAL_CODEX_BIN: fakeBin,
208+
});
209+
210+
expect(result.status).toBe(0);
211+
expect(result.stdout).toContain(
212+
'FORWARDED:exec status -c cli_auth_credentials_store="file"',
213+
);
214+
});
215+
216+
it("skips file auth store forwarding when the opt-out env var is disabled", () => {
217+
const fixtureRoot = createWrapperFixture();
218+
const fakeBin = createFakeCodexBin(fixtureRoot);
219+
const result = runWrapper(fixtureRoot, ["exec", "status"], {
220+
CODEX_MULTI_AUTH_REAL_CODEX_BIN: fakeBin,
221+
CODEX_MULTI_AUTH_FORCE_FILE_AUTH_STORE: "0",
222+
});
223+
224+
expect(result.status).toBe(0);
225+
expect(result.stdout).toContain("FORWARDED:exec status");
226+
expect(result.stdout).not.toContain('cli_auth_credentials_store="file"');
227+
});
228+
188229
it("installs Windows codex shell guards to survive shim takeover", () => {
189230
if (process.platform !== "win32") {
190231
return;
@@ -206,7 +247,8 @@ describe("codex bin wrapper", () => {
206247
);
207248
writeFileSync(
208249
join(shimDir, "codex.ps1"),
209-
'Write-Output "$basedir/node_modules/@openai/codex/bin/codex.js"' + "\r\n",
250+
'Write-Output "$basedir/node_modules/@openai/codex/bin/codex.js"' +
251+
"\r\n",
210252
"utf8",
211253
);
212254

@@ -246,21 +288,36 @@ describe("codex bin wrapper", () => {
246288
expect(readFileSync(pwshProfilePath, "utf8")).toContain(
247289
"# >>> codex-multi-auth shell guard >>>",
248290
);
249-
expect(readFileSync(pwshProfilePath, "utf8")).toContain("CodexMultiAuthShim");
291+
expect(readFileSync(pwshProfilePath, "utf8")).toContain(
292+
"CodexMultiAuthShim",
293+
);
250294
});
251295

252296
it("prefers invocation-derived shim directory over PATH-decoy shim entries", () => {
253297
if (process.platform !== "win32") {
254298
return;
255299
}
256300

257-
const fixtureRoot = mkdtempSync(join(tmpdir(), "codex-wrapper-invoke-fixture-"));
301+
const fixtureRoot = mkdtempSync(
302+
join(tmpdir(), "codex-wrapper-invoke-fixture-"),
303+
);
258304
createdDirs.push(fixtureRoot);
259305
const globalShimDir = join(fixtureRoot, "global-bin");
260-
const scriptDir = join(globalShimDir, "node_modules", "codex-multi-auth", "scripts");
306+
const scriptDir = join(
307+
globalShimDir,
308+
"node_modules",
309+
"codex-multi-auth",
310+
"scripts",
311+
);
261312
mkdirSync(scriptDir, { recursive: true });
262-
copyFileSync(join(repoRootDir, "scripts", "codex.js"), join(scriptDir, "codex.js"));
263-
copyFileSync(join(repoRootDir, "scripts", "codex-routing.js"), join(scriptDir, "codex-routing.js"));
313+
copyFileSync(
314+
join(repoRootDir, "scripts", "codex.js"),
315+
join(scriptDir, "codex.js"),
316+
);
317+
copyFileSync(
318+
join(repoRootDir, "scripts", "codex-routing.js"),
319+
join(scriptDir, "codex-routing.js"),
320+
);
264321
writeFileSync(
265322
join(globalShimDir, "codex-multi-auth.cmd"),
266323
"@ECHO OFF\r\nREM real shim\r\n",
@@ -285,7 +342,9 @@ describe("codex bin wrapper", () => {
285342
expect(readFileSync(join(globalShimDir, "codex.bat"), "utf8")).toContain(
286343
"codex-multi-auth windows shim guardian v1",
287344
);
288-
expect(() => readFileSync(join(decoyShimDir, "codex.bat"), "utf8")).toThrow();
345+
expect(() =>
346+
readFileSync(join(decoyShimDir, "codex.bat"), "utf8"),
347+
).toThrow();
289348
});
290349

291350
it("honors bypass for auth commands and forwards to the real CLI", () => {
@@ -369,20 +428,20 @@ describe("codex bin wrapper", () => {
369428
it("prints actionable guidance when real codex bin cannot be found", () => {
370429
const fixtureRoot = createWrapperFixture();
371430
const missingOverride = join(fixtureRoot, "missing", "codex.js");
372-
const result = runWrapper(
373-
fixtureRoot,
374-
["--version"],
375-
{
376-
CODEX_MULTI_AUTH_BYPASS: "",
377-
CODEX_MULTI_AUTH_REAL_CODEX_BIN: missingOverride,
378-
},
379-
);
431+
const result = runWrapper(fixtureRoot, ["--version"], {
432+
CODEX_MULTI_AUTH_BYPASS: "",
433+
CODEX_MULTI_AUTH_REAL_CODEX_BIN: missingOverride,
434+
});
380435
const output = combinedOutput(result);
381436

382437
expect(result.status).toBe(1);
383-
expect(output).toContain(`CODEX_MULTI_AUTH_REAL_CODEX_BIN is set but missing: ${missingOverride}`);
438+
expect(output).toContain(
439+
`CODEX_MULTI_AUTH_REAL_CODEX_BIN is set but missing: ${missingOverride}`,
440+
);
384441
expect(output).toContain("Could not locate the official Codex CLI binary");
385-
expect(output).toContain("Install it globally: npm install -g @openai/codex");
442+
expect(output).toContain(
443+
"Install it globally: npm install -g @openai/codex",
444+
);
386445
});
387446

388447
it("handles concurrent wrapper invocations without module-load regressions", async () => {

test/documentation.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,12 @@ describe("Documentation Integrity", () => {
228228
expect(storagePaths).toContain('cli_auth_credentials_store="file"');
229229
expect(storagePaths).toContain("CODEX_MULTI_AUTH_FORCE_FILE_AUTH_STORE=0");
230230
expect(storagePaths).toContain("outside interactive terminals");
231+
expect(storagePaths).toContain("Windows note:");
232+
expect(storagePaths).toContain("EPERM");
233+
expect(storagePaths).toContain("EBUSY");
234+
expect(storagePaths).toContain(
235+
"does not rewrite or expose previously written CLI auth files",
236+
);
231237
expect(storagePaths).toContain("letters, numbers, `_`, and `-`");
232238
expect(storagePaths).toContain(
233239
"global root: `~/.codex/multi-auth/backups/<name>.json`",
@@ -336,6 +342,7 @@ describe("Documentation Integrity", () => {
336342
expect(UI_COPY.settings.backendHelp).toBe(
337343
"Enter Open | 1-4 Category | S Save | R Reset | Q Back (No Save)",
338344
);
345+
expect(UI_COPY.settings.backendHelp).not.toContain("`+` / `-`");
339346
});
340347

341348
it("keeps settings reference sections aligned with current menu labels and backend categories", () => {

0 commit comments

Comments
 (0)