Skip to content

fix(codex): BSD-safe temp-file creation on macOS (mktemp suffix + trailing-slash TMP_ROOT)#2103

Open
ShuratCode wants to merge 1 commit into
garrytan:mainfrom
ShuratCode:fix/codex-bsd-mktemp-2091
Open

fix(codex): BSD-safe temp-file creation on macOS (mktemp suffix + trailing-slash TMP_ROOT)#2103
ShuratCode wants to merge 1 commit into
garrytan:mainfrom
ShuratCode:fix/codex-bsd-mktemp-2091

Conversation

@ShuratCode

Copy link
Copy Markdown

Fixes #2091.

On macOS, /codex (all three modes) fails before Codex ever runs: the mktemp
calls that create the stderr/prompt/response temp files error out, the bash block
exits 1, and the user gets no review. Two compounding but independent bugs,
both in gstack. Diagnosis credit to Scott Hardin (@scotthardin) in #2091
this PR implements his suggested fix.

Root causes

1. Suffix after the mktemp placeholder (BSD vs GNU).
The codex skill used templates like mktemp "$TMP_ROOT/codex-err-XXXXXX.txt"
(also codex-prompt-XXXXXX.txt, codex-resp-XXXXXX.txt). GNU mktemp tolerates
a suffix after the XXXXXX run; BSD mktemp (macOS) requires the placeholder
at the end of the template and does not substitute the X's at all when a suffix
follows.
So call #1 creates a literal fixed-name file codex-err-XXXXXX.txt
and exits 0, and a later call — a second /codex run, a stale leftover, or a
concurrent worktree — fails with mkstemp failed: File exists, exits 1, and the
empty TMPERR/_PROMPT_FILE vars cascade into "No such file or directory". This
is why the failure is state-dependent, and why some macOS users think
/codex "works."

2. Trailing slash in TMP_ROOT.
bin/gstack-paths emitted TMP_ROOT straight from $TMPDIR, which on macOS ends
in / (e.g. /var/folders/.../T/), so "$TMP_ROOT/codex-err-…" expands to a
double-slash path (…/T//codex-err-…), compounding the failure.

The fix

  • bin/gstack-paths: strip the trailing slash from TMP_ROOT at the source
    (_tmp_root="${_tmp_root%/}"), so every consumer benefits, not just
    /codex. A bare / is preserved (not collapsed to empty).
  • codex/SKILL.md.tmpl (+ regenerated codex/SKILL.md): move the placeholder
    to the end of every codex mktemp template (drop the .txt suffix), at all
    five sites (codex-err ×3, codex-prompt ×1, codex-resp ×1).

Both changes are needed — the suffix bug is independent of the slash bug.

Verified repro (macOS 26.x, Apple Silicon, gstack 1.58.4.0, /usr/bin/mktemp = BSD, $TMPDIR ends in /)

Before (old template, BSD mktemp):

call#1 -> rc=0 path=…/T/codex-err-XXXXXX.txt      # literal name, X's NOT substituted
call#2 -> rc=1 mktemp: mkstemp failed on …/T//codex-err-XXXXXX.txt: File exists
files created:  codex-err-XXXXXX.txt

After (new template + slash-normalized root):

TMP_ROOT = …/T                                    # no trailing slash, no double slash
call#1 -> rc=0 path=…/T/codex-err-YNRdRX          # real randomized name
call#2 -> rc=0 path=…/T/codex-err-QfpaqE          # survives — no "File exists"

Tests

test/regression-issue2091-codex-bsd-mktemp.test.ts pins both bugs:

  • bug 1 — static invariant: no codex mktemp template carries a non-X
    suffix after XXXXXX, asserted across both codex/SKILL.md.tmpl and the
    generated codex/SKILL.md so regen drift (or editing one but not the other)
    can't reopen it.
  • bug 2 — runtime: gstack-paths strips a trailing slash from TMPDIR/TMP,
    leaves a clean path unchanged, and never collapses / to empty.
bun test test/regression-issue2091-codex-bsd-mktemp.test.ts test/gstack-paths.test.ts \
         test/codex-hardening.test.ts test/gen-skill-docs.test.ts test/skill-validation.test.ts
# 778 pass, 0 fail

codex/SKILL.md was regenerated via bun run gen:skill-docs --host all, so the
skill-docs dry-run CI check stays green.

Same-class occurrences found — left out of scope (follow-ups)

Grepping mktemp .*XXXXXX\.(txt|json|md) repo-wide surfaced the same
suffix-after-placeholder antipattern elsewhere. These are not on the /codex
path (#2091) and use hardcoded /tmp rather than $TMP_ROOT, so I kept this PR
focused and reviewable rather than silently expanding it:

  • claude/SKILL.md.tmpl:98-99mktemp /tmp/gstack-claude-response-XXXXXX.json
    and mktemp /tmp/gstack-claude-error-XXXXXX.txt.
  • scripts/resolvers/review.ts:354mktemp /tmp/gstack-codex-oh-XXXXXXXX.txt
    (this is the source that generates office-hours/SKILL.md:1303).

Happy to fold these into this PR or send a follow-up — your call.

…iling-slash TMP_ROOT)

On macOS, /codex (all three modes) failed before Codex ever ran: the mktemp
calls that create the stderr/prompt/response temp files errored out, the bash
block exited 1, and the user got no review. Two compounding, independent bugs.

1. Suffix after the placeholder. The codex skill used templates like
   `mktemp "$TMP_ROOT/codex-err-XXXXXX.txt"` (also codex-prompt / codex-resp).
   GNU mktemp tolerates a suffix after the X run, but BSD mktemp (macOS) does
   not substitute the X's at all when the placeholder isn't at the end — so
   call garrytan#1 creates a LITERAL `codex-err-XXXXXX.txt` (exit 0) and a later call
   (a second /codex run, a stale leftover, or a concurrent worktree) fails with
   `mkstemp failed: File exists` and aborts. The failure is state-dependent,
   which is why some macOS users saw /codex "work."
   Fix: move the placeholder to the end of every codex mktemp template (drop
   the `.txt` suffix) in codex/SKILL.md.tmpl, regenerate codex/SKILL.md.

2. Trailing slash in TMP_ROOT. bin/gstack-paths emitted TMP_ROOT straight from
   $TMPDIR, which on macOS ends in `/` (e.g. /var/folders/.../T/), so
   "$TMP_ROOT/codex-err-..." expanded with a double slash.
   Fix: strip the trailing slash at the source so every consumer benefits, not
   just /codex (a bare "/" is preserved, not collapsed to empty).

Both fixes are needed; the suffix bug is independent of the slash bug. Adds
test/regression-issue2091-codex-bsd-mktemp.test.ts: a static invariant that no
codex mktemp template carries a suffix after XXXXXX (asserted across both the
.tmpl and the generated SKILL.md so regen drift can't reopen it), plus runtime
checks that gstack-paths normalizes the trailing slash.

Reported-by: Scott Hardin (@scotthardin)
Refs garrytan#2091

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jun 24, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/codex temp-file creation fails on macOS (BSD mktemp): .txt suffix after XXXXXX + trailing-slash TMP_ROOT

2 participants