Summary
This is a follow-up to #5871 after debugging the same workflow on Open Design Desktop 0.17.0 and reproducing it again after the automatic update to 0.18.0.
On Windows, a Codex-driven gpt-image-2 media task can finish successfully and write a valid PNG, while media wait exits with code 0 but its final {"file": ...} JSON is missing from captured stdout. The outer agent then checks the wrong project directory, concludes that no PNG exists, reports failure, and may submit a duplicate generation job.
This is not caused by a third-party gpt-image-2 skill. The third-party skill was removed; the reproduction uses Open Design's media dispatcher and Codex's official system imagegen skill.
Environment
- Open Design Desktop: initially 0.17.0; reproduced on 0.18.0 stable
- Platform: Windows x64 / PowerShell
- Codex CLI: 0.146.0
- Media route/model: Codex subscription /
gpt-image-2
- Third-party image skill: removed
Reproduction
- Create an image project in Open Design.
- Ask the Codex agent to generate one PNG with
gpt-image-2.
- Let it call
media generate, then media wait.
- Capture the wait command with PowerShell, for example:
$stdout = & $env:OD_NODE_BIN $env:OD_BIN media wait ...
$exitCode = $LASTEXITCODE
- Compare the agent output with the daemon log and Open Design's internal project storage.
Actual behavior
Two consecutive jobs in one reproduction both completed successfully:
[task <id-1>] done size=3216754 mime=image/png elapsed=263s
[task <id-2>] done size=2946799 mime=image/png elapsed=419s
Both files were valid, readable 1254×1254 PNGs with the correct PNG signature.
However, the PowerShell caller observed this on each completed wait:
The agent therefore reported that generation failed. After the first successful task was misclassified, it submitted a second generation request.
The outer Open Design run itself ended with status: succeeded / exit 0, but its final assistant message said the image generation failed and exposed no artifact.
Expected behavior
media generate should immediately return a structured taskId on stdout.
media wait should always preserve its terminal JSON:
- exit
0 + {"file": ...} when complete;
- exit
2 + {"taskId": ..., "status": "running", ...} while running;
- a non-zero failure code + structured error JSON on failure.
- A successful task should be surfaced as a project artifact without requiring the agent to find the file manually.
- One requested output should create at most one active media task.
Confirmed contributing bugs
1. Final stdout JSON can be lost on process exit
The CLI writes terminal output using process.stdout.write(...) and then exits directly with process.exit(...). The top-level dispatcher also forces process.exit(process.exitCode ?? 0).
On Windows, when stdout is captured through a PowerShell pipeline/assignment, the process can terminate before the pipe flushes. This explains the observed exit=0 with empty stdout even though the completion branch ran.
This is related to the lost-output symptom in #5728, but this reproduction is on the successful task path, not the failure/libuv-crash path.
2. The initial async handoff is delayed and split across streams
media generate logs the queued task ID to stderr immediately, but the structured task JSON on stdout is emitted only after the foreground polling budget. In the agent environment, the foreground tool call can be cut off before that handoff is captured.
A caller that captures only stdout cannot reliably retain the task ID. Merging stderr into stdout is also fragile on Windows and conflicts with earlier issues such as #5731.
3. The documented project directory is not where the daemon writes the media file
The agent is told that OD_PROJECT_DIR is the project files directory and checks it for the generated PNG. In this reproduction, the daemon wrote the successful media result to Open Design's internal project storage instead.
Consequently, a valid completed result is rejected because it is not present in OD_PROJECT_DIR. The terminal file object / media task record should be authoritative, and the path contract should be consistent.
4. No idempotency or active-task recovery
When the handoff/result JSON is lost, there is no client request ID or supported lookup such as “find the active task for this run/project/output”. The agent's only apparent recovery is another media generate, which creates duplicate work.
This continues the deduplication concern in #5871 even when the provider task itself succeeds.
5. The 300-second timeout covers the whole Codex child process
In an earlier reproduction, the image had already been generated, but Codex continued local post-processing. Open Design killed the entire child at the fixed 300-second timeout:
codex imagegen timed out after 300000ms
A second attempt happened to finish sooner. The timeout should be configurable and ideally phase/progress-aware rather than a hard limit over the whole child process.
6. Packaged prompt conflicts with Windows execution and async behavior
The packaged prompt describes a POSIX/bash shell and a long generate → wait loop, while the actual runtime is PowerShell. It also encourages keeping a long polling loop inside one foreground tool call, which is incompatible with the short foreground execution window.
Moving to separate short media wait calls avoids truncation, but it produces dozens of agent turns and millions of repeated input/cached tokens for a several-minute image task. A first-class asynchronous watcher/subscription would be much more reliable and efficient.
Locally validated workaround
The following local changes made the PowerShell contract reliable:
- Return immediately from
media generate with:
{"taskId":"<id>","status":"queued","nextSince":0,"elapsed":0}
- Make each
media wait bounded and return exit 2 with structured running JSON.
- Replace direct branch-level
process.exit(...) calls with process.exitCode = ...; return.
- Flush both output streams before the final process exit:
await Promise.all([
new Promise((resolve) => process.stdout.write("", resolve)),
new Promise((resolve) => process.stderr.write("", resolve)),
]);
process.exit(process.exitCode ?? 0);
- Treat the terminal
file JSON as authoritative instead of rejecting it based on OD_PROJECT_DIR.
- Increase
OD_CODEX_IMAGEGEN_TIMEOUT_MS locally and correct the packaged Windows/PowerShell instructions.
After these changes, exact PowerShell capture tests produced:
- generate: exit
0, one stdout JSON record containing taskId;
- wait/running: exit
2, one stdout JSON record;
- wait/done: exit
0, one stdout JSON record containing file.
Suggested upstream fix
- Make the task handoff immediate, structured, and stdout-only.
- Avoid direct
process.exit() before stdout/stderr drain; preserve JSON on every terminal path.
- Add an idempotency/client-request key and an API to recover active tasks.
- Make the returned media task/file record authoritative and align it with
OD_PROJECT_DIR.
- Surface completed media tasks as run artifacts even if the agent response is wrong or interrupted.
- Replace agent-driven high-frequency polling with a persistent daemon/UI watcher or resumable async wait.
- Make the Codex imagegen timeout configurable and progress-aware.
- Ship platform-specific media instructions (PowerShell on Windows) with one dispatcher command per tool call.
Related issues
User-specific paths, full task/run IDs, hashes, and prompt content have been omitted.
Summary
This is a follow-up to #5871 after debugging the same workflow on Open Design Desktop 0.17.0 and reproducing it again after the automatic update to 0.18.0.
On Windows, a Codex-driven
gpt-image-2media task can finish successfully and write a valid PNG, whilemedia waitexits with code0but its final{"file": ...}JSON is missing from captured stdout. The outer agent then checks the wrong project directory, concludes that no PNG exists, reports failure, and may submit a duplicate generation job.This is not caused by a third-party
gpt-image-2skill. The third-party skill was removed; the reproduction uses Open Design's media dispatcher and Codex's official systemimagegenskill.Environment
gpt-image-2Reproduction
gpt-image-2.media generate, thenmedia wait.Actual behavior
Two consecutive jobs in one reproduction both completed successfully:
Both files were valid, readable 1254×1254 PNGs with the correct PNG signature.
However, the PowerShell caller observed this on each completed wait:
The agent therefore reported that generation failed. After the first successful task was misclassified, it submitted a second generation request.
The outer Open Design run itself ended with
status: succeeded/ exit0, but its final assistant message said the image generation failed and exposed no artifact.Expected behavior
media generateshould immediately return a structuredtaskIdon stdout.media waitshould always preserve its terminal JSON:0+{"file": ...}when complete;2+{"taskId": ..., "status": "running", ...}while running;Confirmed contributing bugs
1. Final stdout JSON can be lost on process exit
The CLI writes terminal output using
process.stdout.write(...)and then exits directly withprocess.exit(...). The top-level dispatcher also forcesprocess.exit(process.exitCode ?? 0).On Windows, when stdout is captured through a PowerShell pipeline/assignment, the process can terminate before the pipe flushes. This explains the observed
exit=0with empty stdout even though the completion branch ran.This is related to the lost-output symptom in #5728, but this reproduction is on the successful task path, not the failure/libuv-crash path.
2. The initial async handoff is delayed and split across streams
media generatelogs the queued task ID to stderr immediately, but the structured task JSON on stdout is emitted only after the foreground polling budget. In the agent environment, the foreground tool call can be cut off before that handoff is captured.A caller that captures only stdout cannot reliably retain the task ID. Merging stderr into stdout is also fragile on Windows and conflicts with earlier issues such as #5731.
3. The documented project directory is not where the daemon writes the media file
The agent is told that
OD_PROJECT_DIRis the project files directory and checks it for the generated PNG. In this reproduction, the daemon wrote the successful media result to Open Design's internal project storage instead.Consequently, a valid completed result is rejected because it is not present in
OD_PROJECT_DIR. The terminalfileobject / media task record should be authoritative, and the path contract should be consistent.4. No idempotency or active-task recovery
When the handoff/result JSON is lost, there is no client request ID or supported lookup such as “find the active task for this run/project/output”. The agent's only apparent recovery is another
media generate, which creates duplicate work.This continues the deduplication concern in #5871 even when the provider task itself succeeds.
5. The 300-second timeout covers the whole Codex child process
In an earlier reproduction, the image had already been generated, but Codex continued local post-processing. Open Design killed the entire child at the fixed 300-second timeout:
A second attempt happened to finish sooner. The timeout should be configurable and ideally phase/progress-aware rather than a hard limit over the whole child process.
6. Packaged prompt conflicts with Windows execution and async behavior
The packaged prompt describes a POSIX/bash shell and a long generate → wait loop, while the actual runtime is PowerShell. It also encourages keeping a long polling loop inside one foreground tool call, which is incompatible with the short foreground execution window.
Moving to separate short
media waitcalls avoids truncation, but it produces dozens of agent turns and millions of repeated input/cached tokens for a several-minute image task. A first-class asynchronous watcher/subscription would be much more reliable and efficient.Locally validated workaround
The following local changes made the PowerShell contract reliable:
media generatewith:{"taskId":"<id>","status":"queued","nextSince":0,"elapsed":0}media waitbounded and return exit2with structured running JSON.process.exit(...)calls withprocess.exitCode = ...; return.fileJSON as authoritative instead of rejecting it based onOD_PROJECT_DIR.OD_CODEX_IMAGEGEN_TIMEOUT_MSlocally and correct the packaged Windows/PowerShell instructions.After these changes, exact PowerShell capture tests produced:
0, one stdout JSON record containingtaskId;2, one stdout JSON record;0, one stdout JSON record containingfile.Suggested upstream fix
process.exit()before stdout/stderr drain; preserve JSON on every terminal path.OD_PROJECT_DIR.Related issues
UV_HANDLE_CLOSINGassertion, exit -1073740791) — hides upstream error and empties stdout/stderr #5728 — output loss on the failure/crash pathOD_NODE_BINpoints toOpen Design.exe(Electron GUI), so agent-drivenmedia generatesilently no-ops (exit 0, no output, no file) #5731 / Windows 0.16.1: bundlednode.exemissing fromresources/open-design/bin/, so the #5731 fix (OD_NODE_BIN) doesn't take effect #6065 — WindowsOD_NODE_BINlauncher issuesUser-specific paths, full task/run IDs, hashes, and prompt content have been omitted.