Codex and Grok client TOML was parsed with smol-toml's default reader, which rejects any integer beyond JavaScript's safe Number range. Valid TOML 1.0 Int64 values such as `max_bytes = 9223372036854775807` made setup, doctor and uninstall treat the whole file as malformed. The client TOML owner now parses with `integersAsBigInt: "asNeeded"`, keeping safe integers as numbers and larger ones exact as bigint, and rejects values outside signed Int64 before any write. All TOML parses in the document owner (document read, inline string decoding and the preserving-edit check) use that reader. The inline-table writer emits bigint values, and setup and doctor accept a positive bigint `startup_timeout_sec`/`tool_timeout_sec` instead of replacing or rejecting it. Fixes #4917 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.4 KiB
TypeScript
119 lines
4.4 KiB
TypeScript
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const environmentFor = (home: string) => ({
|
|
HOME: home,
|
|
USERPROFILE: home,
|
|
CODEX_HOME: join(home, ".codex"),
|
|
XDG_CONFIG_HOME: join(home, ".config"),
|
|
TMPDIR: home,
|
|
});
|
|
|
|
const setupArguments = [
|
|
"setup",
|
|
"--client",
|
|
"codex",
|
|
"--skill=false",
|
|
"--json",
|
|
] as const;
|
|
|
|
cliTest(
|
|
"codex setup and uninstall preserve signed 64-bit TOML integers exactly",
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-toml-int64-");
|
|
const config = join(home, ".codex/config.toml");
|
|
const original =
|
|
"[history]\n" +
|
|
"max_bytes = 9223372036854775807 # Int64 maximum\n" +
|
|
"floor = -9223372036854775808\n" +
|
|
"spelled = 0x7fff_ffff_ffff_ffff\n";
|
|
await mkdir(dirname(config), { recursive: true });
|
|
await writeFile(config, original);
|
|
const run = (arguments_: readonly string[]) =>
|
|
cli.run({
|
|
arguments: arguments_,
|
|
cwd: home,
|
|
environment: environmentFor(home),
|
|
});
|
|
|
|
const preview = await run([...setupArguments, "--dry-run"]);
|
|
expect(preview.exitCode, preview.stdout).toBe(0);
|
|
expect(preview.json).toMatchObject({ status: "planned" });
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
|
|
const applied = await run([...setupArguments, "--yes"]);
|
|
expect(applied.exitCode, applied.stdout).toBe(0);
|
|
expect(applied.json).toMatchObject({
|
|
status: "ready",
|
|
clients: { codex: { status: "configured" } },
|
|
});
|
|
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
|
const configured = await readFile(config, "utf8");
|
|
expect(configured.startsWith(original)).toBe(true);
|
|
expect(configured).toContain("[mcp_servers.rea]");
|
|
|
|
const repeated = await run([...setupArguments, "--yes"]);
|
|
expect(repeated.exitCode, repeated.stdout).toBe(0);
|
|
expect(repeated.json).toMatchObject({
|
|
status: "ready",
|
|
appliedActions: [],
|
|
});
|
|
expect(await readFile(config, "utf8")).toBe(configured);
|
|
|
|
const removed = await run(["uninstall", "--json"]);
|
|
expect(removed.exitCode, removed.stdout).toBe(0);
|
|
expect(removed.json).toMatchObject({ status: "complete" });
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"codex setup keeps exact integers and a large REA timeout in an inline server table",
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-toml-int64-inline-");
|
|
const config = join(home, ".codex/config.toml");
|
|
const original =
|
|
"# retained comment\n" +
|
|
'mcp_servers = { other = { command = "other", budget = 9007199254740993 }, ' +
|
|
'rea = { command = "previous", tool_timeout_sec = 9223372036854775807 } }\n';
|
|
await mkdir(dirname(config), { recursive: true });
|
|
await writeFile(config, original);
|
|
const applied = await cli.run({
|
|
arguments: [...setupArguments, "--yes"],
|
|
cwd: home,
|
|
environment: environmentFor(home),
|
|
});
|
|
expect(applied.exitCode, applied.stdout).toBe(0);
|
|
const configured = await readFile(config, "utf8");
|
|
expect(configured.startsWith("# retained comment\n")).toBe(true);
|
|
expect(configured).toContain("budget = 9007199254740993");
|
|
expect(configured).toContain("tool_timeout_sec = 9223372036854775807");
|
|
expect(configured).not.toContain('"previous"');
|
|
},
|
|
);
|
|
|
|
for (const value of ["9223372036854775808", "-9223372036854775809"])
|
|
cliTest(
|
|
`codex setup rejects ${value} outside Int64 before writing`,
|
|
async ({ cli }) => {
|
|
const home = await createTestTempDirectory("rea-toml-int64-range-");
|
|
const config = join(home, ".codex/config.toml");
|
|
const original = `[history]\nmax_bytes = ${value}\n`;
|
|
await mkdir(dirname(config), { recursive: true });
|
|
await writeFile(config, original);
|
|
const applied = await cli.run({
|
|
arguments: [...setupArguments, "--yes"],
|
|
cwd: home,
|
|
environment: environmentFor(home),
|
|
});
|
|
expect(applied.exitCode, applied.stdout).toBe(1);
|
|
expect(applied.json).toMatchObject({ status: "needs_human" });
|
|
expect(applied.stdout).toContain("outside the signed 64-bit range");
|
|
expect(await readFile(config, "utf8")).toBe(original);
|
|
},
|
|
);
|