Baobaobaolin.com
date
entry
039
topic
tooling
rev
—

codex exec with -i before the prompt: the prompt becomes a second image path

Move -i from after the prompt to before it and the same command says "No prompt provided via stdin". -i takes several values at once. When stdin has content it's worse: no error, your prompt is just replaced.

The way I used to ask Codex to look at an image was codex exec "<prompt>" -i <image>: prompt first, image after. At 00:32 on October 13, a session making illustrations for the company website wrote it the other way round, -i <reference image> "<prompt>", with stdin redirected to /dev/null per my command template.

It never started. The log had two lines, Reading prompt from stdin... and No prompt provided via stdin., even though the prompt was right there on the command line. This note is about where that prompt went. In order: how to reproduce it, why it gets swallowed, what happens when stdin has content or stays open, and three orderings that don't break.

Reproducing it: done in 0.2 seconds

The fix that night was to move --image after the prompt; the same session re-ran it, and about a minute later the tool reported the image was generated. To isolate the behaviour I re-ran the same ordering on codex-cli 0.159.2 with an 8×8 red PNG (red.png):

$ codex exec --skip-git-repo-check -s read-only -i red.png "Reply with exactly: OK" < /dev/null
Reading prompt from stdin...
No prompt provided via stdin.
$ echo $?
1

The whole command took 0.23 seconds and stopped at the prompt-reading stage, with no model response. It didn't hang and it didn't complain about the prompt; it only said there was no prompt on stdin. Move the prompt in front of -i red.png and it answers normally (measured in the last section).

Why it's swallowed: -i takes several values

Two parts of codex exec --help, read together, explain it:

Usage: codex exec [OPTIONS] [PROMPT]

  [PROMPT]
          Initial instructions for the agent. If not provided as an argument (or if `-` is used),
          instructions are read from stdin. ...

  -i, --image <FILE>...
          Optional image(s) to attach to the initial prompt

The three dots in <FILE>... mean the option takes multiple values. In this example, the non-option arguments right after -i are taken one by one as image paths (the -- and = forms below separate them). So -i red.png "Reply with exactly: OK" is two image paths to it: red.png and a file named Reply with exactly: OK (the second fails to read). The positional PROMPT is left empty, and per the first sentence of its help text, it reads stdin instead.

-i red.png red.png "Reply with exactly: OK" behaves the same, consistent with all three values being collected by -i. Quoting makes the sentence one shell argument; whether it belongs to -i or PROMPT is decided by codex's argument parsing.

When stdin has content: no error, the prompt is replaced

The run above failed only because stdin was empty. With content on stdin that reaches EOF, this test completed, answering a question I didn't ask (stdout only below; startup output on stderr omitted):

$ echo "Reply with exactly: STDIN-OK" | \
    codex exec --skip-git-repo-check -s read-only -i red.png "Reply with exactly: ARG-OK"
STDIN-OK
$ echo $?
0

In that session's record (a rollout JSONL under ~/.codex/sessions/), the user message sent to the model is, in order: the red.png image, a line saying "Codex could not read the local image at `Reply with exactly: ARG-OK`: No such file or directory (os error 2)", and then the stdin text. My prompt reached the model as a missing-image notice. No warning about the unreadable image was printed in the terminal, and the exit code was 0.

That's harder to notice than a 0.2-second failure. Suppose a script feeds a diff on stdin, attaches a screenshot with -i, and follows it with "check whether this change breaks the layout". Going by the behaviour above, the instruction is taken as a file name and the model gets the diff, the screenshot and a missing-file notice. It may still write a report, but your instruction isn't in the normal prompt field, so you can't assume it will do the layout check you asked for (a scenario inferred from the measured behaviour, not something I've run that way).

When stdin stays open: it waits

The third case is a pipe that never closes. As covered in codex exec waiting for stdin's EOF, it reads stdin until EOF. Once -i has taken the prompt, stdin is its only source of one. With sleep 8 | standing in for a pipe held open for eight seconds:

$ sleep 8 | codex exec --skip-git-repo-check -s read-only -i red.png "Reply with exactly: OK"
Reading prompt from stdin...
No prompt provided via stdin.
# measurement note: exit=1, about 8 s

It fails only once the pipe closes. What entry 025 measured still running at 60 seconds was the other path, a prompt given plus extra stdin; this is a different path, but it also waits for EOF. Called from a program that never closes the write end of stdin, going by that mechanism, it would sit at Reading prompt from stdin... and keep waiting. It looks like a hang; in this example the cause is that the prompt was collected on the command line.

Three orderings that keep the prompt

With the same red image and the question "what is the dominant colour of the attached image, reply with one lowercase word", all three answered red with exit code 0, consistent with the image being attached and the prompt being read (the file name also hints at the colour; for a strict check, look for input_image in the session record):

P="What is the dominant color of the attached image? Reply with one lowercase word."

# 1. prompt before -i
codex exec --skip-git-repo-check -s read-only "$P" -i red.png < /dev/null

# 2. -- ends the options; everything after it is positional
codex exec --skip-git-repo-check -s read-only -i red.png -- "$P" < /dev/null

# 3. bind the value to the option with =
codex exec --skip-git-repo-check -s read-only --image=red.png "$P" < /dev/null

My command template now uses the first, because it depends least on remembering how the parser works: an ordinary non-empty prompt in the first positional slot can't be collected by a later -i. The second suits prompts that might start with -; but a prompt that is exactly - still means "read stdin", per the help text. All three keep < /dev/null, so if the order is ever wrong again, it fails right away when fetching the prompt (0.23 seconds in this test) instead of waiting on the caller's pipe or letting stdin stand in for the prompt.

When a session record exists, its user message shows whether the prompt arrived. A line reading "could not read the local image at `…`" with your intended prompt inside the backticks, and no normal prompt, is this problem.

If you remember one thing

In codex exec, separate -i from the prompt with one of the three forms above. The <FILE>... in the help means it takes multiple values; on this version, -i collects the prompt right after it. The night it happened, it failed immediately, which was the lucky version. With content on stdin, the same mistake quietly replaces your prompt.

Revision history

  1. First published