Repository navigation
mktemp templates with a suffix after the X-run break on BSD/macOS (breaks the 2nd /codex run in a session) #2558
Description
Summary
11 mktemp templates across the repo put the X placeholder run somewhere other than the end of the template (e.g. codex-err-XXXXXX.txt). That is a GNU-only construct. On BSD/macOS it silently disables randomization entirely, and the second call in a session fails outright.
The most visible symptom: the second /codex run in a session aborts before Codex is ever invoked, and the skill's own error handling reports it as a Codex/model stall — so it gets diagnosed as an OpenAI problem rather than a template bug.
Root cause
GNU and BSD mktemp disagree about a template with trailing non-X characters.
GNU's man page states the behavior explicitly:
--suffix=SUFFappend SUFF to TEMPLATE; SUFF must not contain a slash.
This option is implied if TEMPLATE does not end in X
GNU quietly reinterprets the trailing .txt as a suffix and randomizes the Xs anyway. BSD mktemp has no --suffix concept at all, so it uses the string literally.
Measured, same template, both binaries (macOS 15, coreutils 9.11 as gmktemp):
$ gmktemp "./codex-err-XXXXXX.txt" # GNU — what CI runs
./codex-err-emiUQv.txt
$ gmktemp "./codex-err-XXXXXX.txt"
./codex-err-RWbt4Q.txt # randomizes fine
$ mktemp "./codex-err-XXXXXX.txt" # BSD — every macOS dev laptop
./codex-err-XXXXXX.txt # literal X's, no randomization
$ mktemp "./codex-err-XXXXXX.txt"
mktemp: mkstemp failed on ./codex-err-XXXXXX.txt: File exists
The first call "succeeds" because the literal name happens to be free, which is exactly why this is invisible until it isn't.
Reproduce
On macOS, run /codex review twice in one session. The second run dies at the TMPERR=$(mktemp ...) line in Step 2A before codex is executed. Because the skill's error handling maps a non-zero exit to the timeout/stall path, the user is told:
Codex stalled past 5.5 minutes. Common causes: model API stall, long prompt, network issue.
Why CI never caught it
.github/workflows/ runs on ubicloud-standard-8 (Linux) and windows-latest. Both get GNU semantics. There is no macOS runner — and macOS is the primary platform for the tool's users. Combined with the bug requiring a second invocation to surface, no first-run smoke test would ever see it.
Affected sites — 11, in 4 source files
The generated SKILL.md files follow from the first three via bun run gen:skill-docs.
| File | Sites | Templates |
|---|---|---|
codex/SKILL.md.tmpl |
5 | codex-err-XXXXXX.txt, codex-prompt-XXXXXX.txt, codex-resp-XXXXXX.txt |
claude/SKILL.md.tmpl |
3 | ...-response-XXXXXX.json, ...-error-XXXXXX.txt, ...-diff-XXXXXX.patch |
scripts/resolvers/review.ts |
1 | gstack-codex-oh-XXXXXXXX.txt (emits the office-hours line) |
bin/gstack-developer-profile |
2 | developer-profile.json.XXXXXX.tmp |
bin/gstack-developer-profile is worth separating out: it is a live executable, not a prompt, and the temp file is the atomic-rename target in the profile migration. A second migration attempt fails rather than merely inconveniencing an agent.
Note that templates ending in Xs are fine regardless of length — mktemp /tmp/codex-design-XXXXXXXX and mktemp -d are unaffected. Only a suffix after the run is broken.
Suggested fix
Move the extension ahead of the placeholder run so the Xs stay terminal, which is valid on both implementations:
-TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")
+TMPERR=$(mktemp "$TMP_ROOT/codex-err-txt-XXXXXX")Nothing in the codebase keys off these extensions — the files are opened by path and read for diagnostics — so dropping the extension entirely would work too.
Suggested guard
The broken form reads as correct to a reviewer, so it will come back without a test. A regex over the affected sources works (mktemp[^|;&\n]*X{3,}[^X"'\s)]), but it needs its own positive control: assert the detector matches -XXXXXX.txt and spares both -txt-XXXXXX and the legitimate 8-X templates, otherwise a zero-result scan proves nothing.
The structural item
Fixing the 11 sites is containment. The reason a GNU-only assumption reached main and survived is that nothing ever executes this code on BSD. Other GNU-isms fail the same silent way — sed -i, readlink -f, date -d all differ between the implementations — and I have not audited the tree for them. A macOS CI runner is the actual fix, with the honest caveat that adding one will likely surface further existing failures and cost CI minutes.
Offer
I have the fix implemented and verified locally against main: all 11 sites, regenerated skill docs, plus the regression test and its positive control described above. Mutation-verified — reintroducing the bug fails exactly the two expected tests (the template and its generated output) and no siblings. Full suite is unchanged before and after: the 5 gstack-gbrain-detect failures on this machine are environment-dependent and present on clean main too.
Happy to open a PR if that is useful.
Activity
schieble commentedon Aug 13, 2026
Follow-up: I ran the BSD/GNU audit the issue said I hadn't, and the result argues against the last section of my own report. Correcting it here rather than letting it stand.
Result: no other GNU-isms in the tree
Checked 18 BSD/GNU divergences across every shell surface —
bin/,hooks/,hosts/,setup,*.sh, and the shell embedded in*.md/*.tmplskill prompts, since agents execute that too. Excluded.git,node_modules,CHANGELOG.md,test/fixtures/, and the generated.cursor//.gbrain//.opencode/mirrors so the count reflects real sites rather than copies.Zero genuine hits:
sed -i(no-ext),date -d,date +%N,grep -P,base64 -w,du -b,gensub/asort,xargs -r,find -printf,head -n -N,cp -T/ln -T,sed -r,seq -w,ps --sort.Bash-4 constructs: also zero —
declare -A,${v,,}/${v^^},mapfile/readarray. Worth stating explicitly because all 61 shell executables run underbash, and macOS ships 3.2.57 (2007). Those three would break on every Mac today. None are present.Four near-misses that are correct code
Each of these matched a naive grep and turned out to be right, so they're worth naming rather than filed as findings:
bin/gstack-repo-modeusesstat -f %m "$f" || stat -c %Y "$f"— BSD first, GNU fallback. Correct pattern.timeouthas 54 occurrences, but_gstack_codex_timeout_wrapperinbin/gstack-codex-proberesolvesgtimeout→timeout→ run-unwrapped, and the only baretimeout Ncalls are insidesetup's apt/dnf/pacman/apk branches, which never execute on macOS.date -dmatch was actuallyglab mr update -d(GitLab's--description).readlink -fappears only in README prose, and current macOS ships-fregardless.Method note, since a wall of zeros is not by itself evidence
Two things that would have made this audit lie:
timeoutandtacresolved to Homebrew GNU binaries that a clean macOS install doesn't have, so they initially tested "fine." Re-resolved every binary and checked GNU-vs-BSD per tool before trusting any of it.Correction to my own recommendation
The original report's last section said other GNU-isms "fail the same silent way" and used that to argue for a macOS CI runner. That framing was speculative and the audit does not support it — there is no backlog of BSD breakage behind this bug.
The honest version: the
mktempissue is an isolated defect, not a symptom. A macOS runner is still reasonable hygiene and would have caught this one, but its value is preventing recurrence, not clearing existing breakage. That makes the 11-site fix plus a regression guard the higher-value change, and the CI runner a separate judgement call about cost — not a prerequisite.garrytan commentedon Aug 17, 2026
Fixed on main in v1.64.0.0/v1.67 — all mktemp templates end in XXXXXX; pinned by test/mktemp-portability.test.ts + regression-issue2091-bsd-mktemp.test.ts. Closing.