fix: ensure resume args precede image args (#10709)

## Summary
Fixes argument ordering when `resumeThread()` is used with
`local_image`. The SDK previously emitted CLI args with `--image` before
`resume <threadId>`, which caused the Codex CLI to treat `resume`/UUID
as image paths and start a new session. This PR moves `resume
<threadId>` before any `--image` flags and adds a regression test.

## Bug Report / Links
- OpenAI issue: https://github.com/openai/codex/issues/10708
- Repro repo:
https://github.com/cryptonerdcn/codex-resume-local-image-repro
- Repro issue (repo):
https://github.com/cryptonerdcn/codex-resume-local-image-repro/issues/1

## Repro (pre-fix)
1. Build SDK from source
2. Run resume + local_image
3. Args order: `--image <path> resume <id>`
4. Result: new session created (thread id changes)

## Fix
Move `resume <threadId>` before `--image` in `CodexExec.run` and add a
regression test to assert ordering.

## Tests
- `cd sdk/typescript && npm test`
  - **Failed**: `codex-rs/target/debug/codex` missing (ENOENT)

## Notes
- I can rerun tests in an environment with `codex-rs` built and report
results.
This commit is contained in:
cryptonerdcn
2026-02-05 14:19:56 +09:00
committed by GitHub
parent 4ed8d74aab
commit 1dc06b6ffc
2 changed files with 30 additions and 4 deletions

View File

@@ -115,16 +115,16 @@ export class CodexExec {
commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`); commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`);
} }
if (args.threadId) {
commandArgs.push("resume", args.threadId);
}
if (args.images?.length) { if (args.images?.length) {
for (const image of args.images) { for (const image of args.images) {
commandArgs.push("--image", image); commandArgs.push("--image", image);
} }
} }
if (args.threadId) {
commandArgs.push("resume", args.threadId);
}
const env: Record<string, string> = {}; const env: Record<string, string> = {};
if (this.envOverride) { if (this.envOverride) {
Object.assign(env, this.envOverride); Object.assign(env, this.envOverride);

View File

@@ -67,4 +67,30 @@ describe("CodexExec", () => {
expect(result.error.message).toMatch(/Codex Exec exited/); expect(result.error.message).toMatch(/Codex Exec exited/);
} }
}); });
it("places resume args before image args", async () => {
const { CodexExec } = await import("../src/exec");
spawnMock.mockClear();
const child = new FakeChildProcess();
spawnMock.mockReturnValue(child as unknown as child_process.ChildProcess);
setImmediate(() => {
child.stdout.end();
child.stderr.end();
child.emit("exit", 0, null);
});
const exec = new CodexExec("codex");
for await (const _ of exec.run({ input: "hi", images: ["img.png"], threadId: "thread-id" })) {
// no-op
}
const commandArgs = spawnMock.mock.calls[0]?.[1] as string[] | undefined;
expect(commandArgs).toBeDefined();
const resumeIndex = commandArgs!.indexOf("resume");
const imageIndex = commandArgs!.indexOf("--image");
expect(resumeIndex).toBeGreaterThan(-1);
expect(imageIndex).toBeGreaterThan(-1);
expect(resumeIndex).toBeLessThan(imageIndex);
});
}); });