From 365d5a584efbb8d8ff3409a3b7fb91446755daae Mon Sep 17 00:00:00 2001 From: 2sumtech <2sumtech@gmail.com> Date: Wed, 16 Sep 2026 10:30:41 -0700 Subject: [PATCH] fix(image): fail when every generated image is rejected The image generation API answers HTTP 200 with an empty `image_urls` list and `success_count: 0` when all requested images are rejected by the content policy or fail in the model. `mmx image generate` ignored `success_count`, so it pushed the `--out` path onto `saved`, printed it as a successful result and exited 0 without writing any file. ERRORS.md already documents the intended behaviour for this scenario: | `success_count === 0` (all rejected) | `Image generation failed: all images were rejected (content policy or model error).` | Reject the response before the download/write step so the documented error and a non-zero exit code are produced instead of a phantom path. Co-Authored-By: Claude Fable 5.1 --- src/commands/image/generate.ts | 10 ++++++++++ test/commands/image/generate.test.ts | 30 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/commands/image/generate.ts b/src/commands/image/generate.ts index b530a72b..e05a0e81 100644 --- a/src/commands/image/generate.ts +++ b/src/commands/image/generate.ts @@ -142,6 +142,16 @@ export default defineCommand({ process.stderr.write('[Model: image-01]\n'); } + // The API answers 200 with an empty image list when every image is rejected. + // Without this check the command reports the --out path as saved and exits 0 + // even though nothing was written (see ERRORS.md, `mmx image generate`). + if (response.data.success_count === 0) { + throw new CLIError( + 'Image generation failed: all images were rejected (content policy or model error).', + ExitCode.GENERAL, + ); + } + const saved: string[] = []; if (outPath) { diff --git a/test/commands/image/generate.test.ts b/test/commands/image/generate.test.ts index 1cdd56a4..fbc4996b 100644 --- a/test/commands/image/generate.test.ts +++ b/test/commands/image/generate.test.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from 'bun:test'; +import { existsSync, rmSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; import { default as generateCommand } from '../../../src/commands/image/generate'; +import { createMockServer, jsonResponse } from '../../helpers/mock-server'; const baseConfig = { apiKey: 'test-key', @@ -91,4 +95,30 @@ describe('image generate command', () => { expect(parsed.request.seed).toBe(42); expect(parsed.request.model).toBe('image-01'); }); + + it('fails when the API rejects every image instead of reporting a saved file', async () => { + const server = createMockServer({ + routes: { + '/v1/image_generation': () => jsonResponse({ + id: 'req-1', + data: { image_urls: [], task_id: 'task-1', success_count: 0, failed_count: 1 }, + base_resp: { status_code: 0, status_msg: 'success' }, + }), + }, + }); + const outPath = join(tmpdir(), `mmx-image-rejected-${process.pid}-${Date.now()}.jpg`); + + try { + await expect( + generateCommand.execute( + { ...baseConfig, baseUrl: server.url, quiet: true }, + { ...baseFlags, quiet: true, prompt: 'A cat', out: outPath }, + ), + ).rejects.toThrow('Image generation failed: all images were rejected'); + expect(existsSync(outPath)).toBe(false); + } finally { + rmSync(outPath, { force: true }); + server.close(); + } + }); });