Skip to content

mktemp templates with a suffix after the X-run break on BSD/macOS (breaks the 2nd /codex run in a session) #2558

Closed

Description

@schieble

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=SUFF append 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

  1. schieble commented on Aug 13, 2026

    @schieble
    Author

    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/*.tmpl skill 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 under bash, 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-mode uses stat -f %m "$f" || stat -c %Y "$f" — BSD first, GNU fallback. Correct pattern.
    • timeout has 54 occurrences, but _gstack_codex_timeout_wrapper in bin/gstack-codex-probe resolves gtimeout → timeout → run-unwrapped, and the only bare timeout N calls are inside setup's apt/dnf/pacman/apk branches, which never execute on macOS.
    • Every date -d match was actually glab mr update -d (GitLab's --description).
    • readlink -f appears only in README prose, and current macOS ships -f regardless.

    Method note, since a wall of zeros is not by itself evidence

    Two things that would have made this audit lie:

    1. The detectors were positive-controlled. All 18 patterns were run against a fixture containing every construct; all 18 fired. A zero-result scan with no proof it can fire isn't a result.
    2. My first ground-truth pass had false negatives. timeout and tac resolved 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 mktemp issue 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.

  2. garrytan commented on Aug 17, 2026

    @garrytan
    Owner

    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.

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

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