Summary
Every mktemp template in codex/SKILL.md.tmpl places the XXXXXX placeholders before a .txt suffix. BSD mktemp (macOS) only substitutes X's when they are the trailing characters of the template. With a suffix it treats the whole string as a literal filename.
The result is not an immediate error, which is what makes it nasty:
- Run 1 creates a file literally named
codex-err-XXXXXX.txt and exits 0. Everything works.
- Run 2 and every run after fail with
mkstemp failed: ... File exists.
Because the literal file persists in $TMPDIR, the skill is broken from the second invocation onward and stays broken until $TMPDIR is cleared.
Why it's worse than a broken temp file
The failure mode defeats the non-zero-exit surfacing added in #1327.
$TMPERR is assigned from the failed mktemp, so it is empty. The redirect 2>"$TMPERR" then fails, codex never runs, and the skill's own handler prints:
with nothing after it, because head -1 "$TMPERR" has no file to read. That is indistinguishable from "the model stalled and returned nothing" — precisely the misdiagnosis #1327's comment says the exit-code surfacing exists to prevent:
Surface non-zero exits (parse errors, arg-shape breaks, etc.) so the calling agent doesn't read "no output" as a silent model/API stall and burn 30-60min misdiagnosing it. See #1327.
I lost a run to exactly this before spotting it.
Reproduction
macOS, BSD mktemp:
$ rm -f "${TMPDIR}demo-XXXXXX.txt"
$ mktemp "${TMPDIR}demo-XXXXXX.txt"; echo "exit=$?"
/var/folders/.../T/demo-XXXXXX.txt
exit=0
$ mktemp "${TMPDIR}demo-XXXXXX.txt"; echo "exit=$?"
mktemp: mkstemp failed on /var/folders/.../T/demo-XXXXXX.txt: File exists
exit=1
Note run 1 created a file whose name contains six literal X characters — no substitution happened.
Correct form:
$ mktemp "${TMPDIR%/}/demo-XXXXXX"
/var/folders/.../T/demo-0v7hIP
Evidence this is happening in the wild
Literal artifacts accumulated in my $TMPDIR from real /codex runs across multiple sessions:
/var/folders/.../T/codex-err-XXXXXX.txt
/var/folders/.../T/codex-err-review-XXXXXX.txt
/var/folders/.../T/codex-out-XXXXXX.txt
/var/folders/.../T/codex-prompt-XXXXXX.txt
codex-err-review-XXXXXX.txt does not match any template in the current skill, so it is left over from an older revision — these have been quietly piling up for a while. They are also never cleaned: rm -f "$TMPERR" removes the literal file, but only on the paths that reach it.
Affected sites
All in codex/SKILL.md.tmpl (the generated codex/SKILL.md mirrors them):
| Line |
Template |
| 161 |
TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt") |
| 209 |
_PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX.txt") |
| 338 |
TMPERR=${TMPERR:-$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")} |
| 437 |
TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX.txt") |
| 438 |
TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt") |
git log -S dates the pattern to d852330 (#197, the original /codex skill), so it predates the $TMP_ROOT refactor in #1056 — the CHANGELOG for that wave shows the prior form was mktemp /tmp/codex-*-XXXXXX.txt, i.e. the suffix came along for the ride rather than being introduced by it.
Secondary: $TMP_ROOT already ends with a slash
bin/gstack-paths emits:
TMP_ROOT=/var/folders/dy/_4txks415gx7mmdct58hmn_m0000gn/T/
so "$TMP_ROOT/codex-err-..." expands to ...T//codex-err-.... Harmless on POSIX, but worth normalizing while touching these lines.
Suggested fix
Move the X's to the end and strip the trailing slash at each of the 5 sites:
-TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")
+TMPERR=$(mktemp "${TMP_ROOT%/}/codex-err-XXXXXX")
If the .txt extension matters to anything downstream, the portable pattern is to create then rename:
TMPERR=$(mktemp "${TMP_ROOT%/}/codex-err-XXXXXX") && mv "$TMPERR" "$TMPERR.txt" && TMPERR="$TMPERR.txt"
Nothing in the skill appears to depend on the extension, so dropping it looks like the simpler correct move.
Worth a grep across other skills for the same shape — I only checked codex/, where it is the sole offender among *.md templates.
Hardening thought, separate from the fix: the [codex exit N] handler could fall back to a message when $TMPERR is empty or unset, so a temp-file failure can never again present as silent model output.
Environment
- gstack
v1.60.1.0 (a3259400)
- macOS,
Darwin 25.5.0 arm64, BSD mktemp (no --version)
- zsh
- Global git install at
~/.claude/skills/gstack, team mode on
Happy to send a PR for the 5-line change if useful.
Summary
Every
mktemptemplate incodex/SKILL.md.tmplplaces theXXXXXXplaceholders before a.txtsuffix. BSDmktemp(macOS) only substitutes X's when they are the trailing characters of the template. With a suffix it treats the whole string as a literal filename.The result is not an immediate error, which is what makes it nasty:
codex-err-XXXXXX.txtand exits 0. Everything works.mkstemp failed: ... File exists.Because the literal file persists in
$TMPDIR, the skill is broken from the second invocation onward and stays broken until$TMPDIRis cleared.Why it's worse than a broken temp file
The failure mode defeats the non-zero-exit surfacing added in #1327.
$TMPERRis assigned from the failedmktemp, so it is empty. The redirect2>"$TMPERR"then fails,codexnever runs, and the skill's own handler prints:with nothing after it, because
head -1 "$TMPERR"has no file to read. That is indistinguishable from "the model stalled and returned nothing" — precisely the misdiagnosis #1327's comment says the exit-code surfacing exists to prevent:I lost a run to exactly this before spotting it.
Reproduction
macOS, BSD
mktemp:Note run 1 created a file whose name contains six literal
Xcharacters — no substitution happened.Correct form:
Evidence this is happening in the wild
Literal artifacts accumulated in my
$TMPDIRfrom real/codexruns across multiple sessions:codex-err-review-XXXXXX.txtdoes not match any template in the current skill, so it is left over from an older revision — these have been quietly piling up for a while. They are also never cleaned:rm -f "$TMPERR"removes the literal file, but only on the paths that reach it.Affected sites
All in
codex/SKILL.md.tmpl(the generatedcodex/SKILL.mdmirrors them):TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")_PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX.txt")TMPERR=${TMPERR:-$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")}TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX.txt")TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")git log -Sdates the pattern to d852330 (#197, the original/codexskill), so it predates the$TMP_ROOTrefactor in #1056 — the CHANGELOG for that wave shows the prior form wasmktemp /tmp/codex-*-XXXXXX.txt, i.e. the suffix came along for the ride rather than being introduced by it.Secondary:
$TMP_ROOTalready ends with a slashbin/gstack-pathsemits:so
"$TMP_ROOT/codex-err-..."expands to...T//codex-err-.... Harmless on POSIX, but worth normalizing while touching these lines.Suggested fix
Move the X's to the end and strip the trailing slash at each of the 5 sites:
If the
.txtextension matters to anything downstream, the portable pattern is to create then rename:Nothing in the skill appears to depend on the extension, so dropping it looks like the simpler correct move.
Worth a grep across other skills for the same shape — I only checked
codex/, where it is the sole offender among*.mdtemplates.Hardening thought, separate from the fix: the
[codex exit N]handler could fall back to a message when$TMPERRis empty or unset, so a temp-file failure can never again present as silent model output.Environment
v1.60.1.0(a3259400)Darwin 25.5.0 arm64, BSDmktemp(no--version)~/.claude/skills/gstack, team mode onHappy to send a PR for the 5-line change if useful.