Skip to content

daemon CLI crashes on media task failure (libuv UV_HANDLE_CLOSING assertion, exit -1073740791) — hides upstream error and empties stdout/stderr #5728

Description

@Bad-Fish666

Summary

When a media task fails (e.g. an upstream 4xx, missing provider key, or unsupported parameter), the daemon CLI (media generate / media wait) prints the failure JSON and then crashes on exit with a native libuv assertion on Windows, returning a garbage exit code (-1073740791 = 0xC0000409) instead of a clean non-zero exit.

Because the process aborts, any harness that captures the CLI's stdout/stderr (redirect to a file, or $out = & node cli …) ends up with empty output — the buffered streams are lost. This makes a genuine upstream failure look like a "silent empty success" (exit 0/garbage, empty STDOUT/STDERR, no file written), which is very hard to diagnose and easily misattributed to the provider.

Environment

  • Open Design 0.15.0 (packaged app)
  • Windows x64, Node 24
  • CLI: prebundled/daemon/daemon-cli.mjs (media generate / media wait)

Observed output (verbatim)

task <id> queued (running)
task failed: custom image 400: { … 'response_format' … }     # or: "no Fal API key — … set FAL_KEY"
{"taskId":"<id>","status":"failed","error":{ … , "status":400}}
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76
# process exit code: -1073740791  (0xC0000409)

Reproduced with two independent failure causes:

  • media generate --model flux-pro-ultra … with no FAL_KEYno Fal API key → crash.
  • media generate --model gpt-image-2 --image ref.png … → upstream 400 (response_format, see companion issue) → crash.

The happy path does not crash: a successful gpt-image-2 text-to-image run exits 0 with the {"file":{…}} JSON and writes the PNG normally.

Steps to reproduce

  1. Run any media generate that will fail upstream (missing key, unsupported param, 4xx).
  2. Capture output, e.g. & $node $cli media generate … *> out.txt or $out = & $node $cli ….
  3. The captured output is empty and the process aborts with the UV_HANDLE_CLOSING assertion instead of a clean exit 5 + preserved error JSON.

Expected

On task failure: clean exit code 5 (task failed) with the error JSON preserved on stdout/stderr. No native crash, no lost output.

Likely cause

An async handle (the SSE/poll socket or a stdout stream) appears to be closed twice / after the event loop is torn down on the failure branch on Windows (libuv uv_close on an already-closing handle). The success branch tears down cleanly; only the error/abort path trips the assertion.

Impact

  • Masks all upstream provider failures behind an unhelpful crash + empty output.
  • Breaks agent runtimes that rely on the documented contract (exit 0 with {file|taskId}, exit 5 on failure): the crash means the agent cannot distinguish "failed" from "produced nothing," and cannot surface the real error to the user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions