can1357/oh-my-pi · error · Error
images.urls.command must reference {file}
Error message
images.urls.command must reference {file} What it means
createCommandUploader requires every command template to include the {file} placeholder, which is substituted with the temporary file path holding the upload bytes. Without {file} the command would never receive the blob to upload, so it throws at creation time. This enforces that the command actually transfers the payload.
Source
Thrown at packages/coding-agent/src/blob-broker/uploaders.ts:84
/** Last URL printed on stdout wins; uploader tools often log progress first. */
export function extractUploadUrl(stdout: string): string | null {
let last: string | null = null;
for (const match of stdout.matchAll(URL_PATTERN)) {
last = match[0].replace(/[)\],.'"]+$/, "");
}
return last;
}
/**
* Build an uploader from an argv template. Placeholders, substituted after
* splitting (paths with spaces stay one argument): `{file}` temp file path,
* `{mime}` MIME type, `{ext}` bare extension.
*/
export function createCommandUploader(template: string): BlobUploader {
const argvTemplate = splitCommandTemplate(template);
if (argvTemplate.length === 0) throw new Error("images.urls.command is empty");
if (!argvTemplate.some(arg => arg.includes("{file}"))) {
throw new Error("images.urls.command must reference {file}");
}
return {
destination: "command",
async upload(request: BlobUploadRequest): Promise<BlobPublication> {
const { bytes, mimeType, extension } = request;
const file = path.join(os.tmpdir(), `omp-blob-upload-${crypto.randomUUID()}.${extension}`);
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([View on GitHub (pinned to 9690622007)
Solutions
- Add {file} to the command where the upload file path belongs, e.g. "curl -F 'file=@{file}' https://0x0.st"
- If the tool reads from stdin, wrap it so it accepts a file argument (e.g. "sh -c 'tool < {file}'" won't work via placeholder substitution — use a tool that takes a path)
- Confirm the placeholder braces are literal {file} (no extra spaces like { file })
- Check splitting behavior: the placeholder can be inside a quoted argument
Example fix
// before
"urls": { "command": "transferwee https://transfer.sh" }
// after
"urls": { "command": "transferwee upload {file}" } Defensive patterns
Strategy: validation
Validate before calling
const cmd = config.images?.urls?.command;
if (typeof cmd === "string" && !cmd.includes("{file}")) {
throw new Error("images.urls.command must reference {file}");
} Try / catch
try {
const uploader = createConfiguredUploader("command", config);
} catch (err) {
if (err instanceof Error && err.message.includes("must reference {file}")) {
// rewrite the command to include {file} where the upload path belongs
}
throw err;
} Prevention
- Always include the literal {file} placeholder in command templates
- Use tools that accept a file path argument (not stdin-only tools)
- Double-check braces: {file} exactly, no spaces inside braces
- Test the expanded command manually with a real file path before committing config
When it happens
Trigger: Configuring images.urls.command with a command lacking {file}, e.g. "images.urls.command": "curl -d @- https://host" or "upload.sh" — any template where no argument contains the literal {file}.
Common situations: Writing a command designed for stdin input instead of a file argument; forgetting the placeholder when adapting an existing upload script; copy-pasting a command from docs that reads the path from an environment variable instead.
Related errors
- images.urls.command is empty
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains an empty provider
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id wit
- ${name} path does not exist: ${trimmed}
- Anthropic thinking budget requires max_tokens greater than $
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/09e159ef5480b66e.
Report an issue: GitHub.