Skip to content

/codex: mktemp templates put X's before a .txt suffix — breaks on macOS after the first run, and the failure looks like a model stall #2370

Description

@wyatt-paxiv

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:

[codex exit 1]

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions