can1357/oh-my-pi · error · Error

${argv[0]} exited with code ${exitCode}: ${stderr.trim().sli

Error message

${argv[0]} exited with code ${exitCode}: ${stderr.trim().slice(-300)}

What it means

The command uploader spawns the configured upload program and captures stdout, stderr, and the exit code. If the program exits with a nonzero code, the error surfaces the program name, exit code, and the last 300 characters of stderr. This propagates the external tool's own failure rather than hiding it.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders.ts:109

			await Bun.write(file, bytes);
			try {
				const argv = argvTemplate.map(arg =>
					arg.replaceAll("{file}", file).replaceAll("{mime}", mimeType).replaceAll("{ext}", extension),
				);
				const cwd = getProjectDir();
				if (!directoryIsEnterableSync(cwd)) {
					throw new Error(`Project directory is not accessible: ${cwd}`);
				}
				const proc = Bun.spawn(argv, { stdin: "ignore", stdout: "pipe", stderr: "pipe", cwd });
				const timeout = setTimeout(() => proc.kill(), UPLOAD_TIMEOUT_MS);
				const [stdout, stderr, exitCode] = await Promise.all([
					new Response(proc.stdout as ReadableStream<Uint8Array>).text(),
					new Response(proc.stderr as ReadableStream<Uint8Array>).text(),
					proc.exited,
				]);
				clearTimeout(timeout);
				if (exitCode !== 0) {
					throw new Error(`${argv[0]} exited with code ${exitCode}: ${stderr.trim().slice(-300)}`);
				}
				const url = extractUploadUrl(stdout);
				if (!url) throw new Error(`${argv[0]} printed no URL on stdout`);
				return { url, destination: "command", bytes: bytes.byteLength };
			} finally {
				await fs.rm(file, { force: true });
			}
		},
	};
}

/**
 * Resolve one registry destination to its built-in uploader.
 *
 * Serving destinations deliberately return `null`; the broker selects those
 * through its separate serve-kind predicate. Registry entries known to be
 * unusable, and active entries without an implementation, fail explicitly
 * before an upload can issue a network request.

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the stderr tail in the error message — it contains the tool's own diagnostic
  2. Run the command manually with a test file, substituting the path for {file}, to reproduce
  3. Check/rotate credentials or tokens for the upload service
  4. Increase headroom for the timeout (upload smaller files / faster network) or verify flag syntax after placeholder substitution

Example fix

// before
"urls": { "command": "curl -F file={file} https://0x0.st" }  // shell parses {file} oddly
// after
"urls": { "command": "curl -sF 'file=@{file}' https://0x0.st" }
Defensive patterns

Strategy: try-catch

Validate before calling

// dry-run the command template before wiring it:
const argv = template.split(" ").map(a => a.replaceAll("{file}", "/tmp/test.png"));
const proc = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe" });
if ((await proc.exited) !== 0) console.error("upload command fails outside the broker too");

Try / catch

try {
	await uploader.upload(req);
} catch (err) {
	if (err instanceof Error && /exited with code/.test(err.message)) {
		// read the stderr tail embedded in the message for the tool's own diagnostic
	}
	throw err;
}

Prevention

When it happens

Trigger: The configured images.urls.command program fails for any reason: bad credentials, network failure, invalid flags (often because {file}/{mime}/{ext} substitution produces an unexpected argument), server rejection of the upload (4xx/5xx), or the program being killed by UPLOAD_TIMEOUT_MS timeout (proc.kill).

Common situations: Expired or wrong upload-service API token; the placeholder-substituted arguments don't match the tool's CLI syntax; rate limiting or file-size limits on the target service; a slow network hitting the upload timeout and killing the process.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/2ca53c0c14e1833c. Report an issue: GitHub.