The morning the agent read every file except the one I asked about
You open the output of an overnight script and find that the aggregation you asked for never happened. What's left is a trail of files the agent wandered through instead. If that has happened to you, you know the reflex: blame the model, then spend an hour rewriting the prompt.
I did the same for a long time. Then I read a GitHub report that made me reconsider the order of my suspicions. The reporter found that with --append-system-prompt placed before -p, the task was effectively ignored and the agent began exploring the repository. With -p placed first, the same model, system prompt, task, tools and repo behaved correctly. Only the position of two flags had changed.
I want to be careful here. Nothing in the report shows this happens in general, and I haven't reproduced it myself. So this isn't a bug explainer. It's a way to settle the question in a few minutes on your own setup.
What the report says, and what it doesn't
| Item | As reported |
|---|---|
| Claude Code | 2.1.274 |
| OS / shell | Windows 11 / PowerShell |
| Model | A local model served through Ollama, reported by Claude Code as an unrecognized custom model |
| Endpoint | A local Anthropic-compatible endpoint |
| Flags | --bare, --output-format stream-json, --verbose |
| Status at the time I read it | Open, no comments |
The reporter says plainly that this was not tested against an Anthropic-hosted model, and that the cause could be argument parsing, print-mode prompt construction, or the compatibility path. That uncertainty is the honest state of things.
What does carry over is the test method: check whether the instruction arrived, using a single sentinel string, before you judge the model.
Measuring delivery, not intelligence
Ask for something trivial: return this exact string and use no tools. If the string appears in the output, the task arrived. If the agent starts exploring, it didn't. There's nothing to interpret.
The script below sends the same task three ways, three times each, and counts how often the sentinel comes back.
#!/usr/bin/env bash
# sentinel-order-check.sh
# Usage: ./sentinel-order-check.sh ./core-agent.md
# Purpose: did the task arrive? (This does not measure model quality.)
set -u
SYS_FILE="${1:?pass the system prompt file}"
SYS_TEXT="$(cat "$SYS_FILE")"
TRIALS=3
# Change the sentinel every run so stale output can't produce a false pass
SENTINEL="PURPLE-ELEPHANT-${RANDOM}${RANDOM}"
TASK="Return exactly this text and nothing else, and do not use any tools: ${SENTINEL}"
count_hits() {
if grep -qF -- "$SENTINEL"; then echo 1; else echo 0; fi
}
run_a() { # A: --append-system-prompt first
claude --append-system-prompt "$SYS_TEXT" -p "$TASK" 2>/dev/null
}
run_b() { # B: -p first
claude -p "$TASK" --append-system-prompt "$SYS_TEXT" 2>/dev/null
}
run_c() { # C: task on stdin, system prompt from a file
printf '%s' "$TASK" | claude -p --append-system-prompt-file "$SYS_FILE" 2>/dev/null
}
for label in a b c; do
hits=0
for _ in $(seq "$TRIALS"); do
n="$(run_"$label" | count_hits)"
hits=$((hits + n))
done
echo "pattern ${label}: ${hits}/${TRIALS}"
doneThree reasons it's written this way. The random sentinel stops a leftover string in a log from passing the check. Three trials per pattern absorbs run-to-run variation, so I don't pin one unlucky call on argument order. And pattern C is a control that depends on neither flag order nor long strings passed as arguments.
Reading the result is simple. If A scores 0/3 while B and C score 3/3, look at ordering or at passing long text as arguments. If all three match, order isn't your problem, and the suspects move to the model, the endpoint, or the contents of the system prompt.
Calling patterns that depend less on order
Whatever your result, a few habits are cheap. These are limited to what the CLI reference and headless-mode docs show.
- Pass the task on stdin. The docs show
cat build-error.txt | claude -p '…', with a 10MB cap on piped input - Load long extra instructions with
--append-system-prompt-fileinstead of embedding them in an argument, which also avoids shell quoting accidents - The docs' own examples write
claude -pfirst and--append-system-promptafter it. When in doubt, mirror them
The reporter ended up with stdin plus a file as well.
# Less order-sensitive: task on stdin, extra instructions from a file
cat ./task.md | claude -p \
--append-system-prompt-file ./prompts/core-agent.md \
--allowedTools "Read" \
--output-format jsonI keep --allowedTools away from the end so that no free-form string follows an option that accepts multiple values. I can't say that's the cause here. It's just a habit that tends to prevent accidents in any CLI.
Make the check part of the script
As an indie developer, I've set up overnight jobs for store-listing drafts in my apps, and I put the same idea at the top of the script: one cheap call that must return a sentinel, and if it doesn't, the real task never starts.
preflight() {
local token="PREFLIGHT-${RANDOM}${RANDOM}"
local out
out="$(printf 'Return only this string: %s' "$token" \
| claude -p --append-system-prompt-file ./prompts/core-agent.md 2>/dev/null)"
if ! grep -qF -- "$token" <<<"$out"; then
echo "preflight failed: instructions did not arrive; not running the task" >&2
return 1
fi
}
preflight || exit 1
# the real task starts hereIt costs one extra call. Finding out in the morning that nothing ran as asked costs far more.
Pitfalls
- Don't hard-code the sentinel. An old log line can make a broken run look healthy
- Judge by presence of the sentinel, not by how plausible the output looks
- With a custom model or compatible endpoint, ordering effects and weaker instruction-following look identical. Run the same check against an Anthropic-hosted model to see whether the difference remains
2>/dev/nullhides errors. Remove it when a check fails
Before rewriting the instruction, confirm it arrived. One word is enough to tell you.
I wrote about recording which model an unattended session actually used in another article.
If you only do one thing today, add the ten-line preflight to a script you already run on a schedule. That's where I'd start.