Skip to content

Commit f836fb2

Browse files
baozhoutaoclaude
andauthored
fix(scripts): put the mktemp X's last in gen-sdui-manifest.sh templates (#17120)
`mktemp "…/sdui-dump-dev.XXXXXX.log"` is a GNU coreutils spelling: coreutils splits the trailing `.log` off as an implied `--suffix` (`--suffix=SUFF … is implied if TEMPLATE does not end in X`) and substitutes the X's anyway, which is why CI has never seen this. BSD mktemp(1) is a thin wrapper over mkstemp(3), which only ever replaces a TRAILING run of X's, so on stock macOS nothing is substituted: the first run creates the file called literally `sdui-dump-dev.XXXXXX.log` and every later run dies at that line with `File exists`. That host is the only one this on-demand ratchet is run on. Two of the three call sites carried the broken shape. The pidfile beside them was already correct and is untouched; it is now the shape all three use. The `.log` extension is given up rather than bought back with `--suffix` (itself the GNU extension) or with a rename that would add a second path and a window in which the two disagree — nothing reads these files by extension. Pins the spelling beside the existing fixed-path pin, asserting the count of GOOD templates next to the count of bad ones so that a file with no templates left in it cannot read as green. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
1 parent 032452a commit f836fb2

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

packages/spec/scripts/gen-sdui-manifest-collision.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,32 @@ function runHarness(): Record<string, string> {
288288
// quietly, since nothing else in the run would notice until two agents
289289
// overlapped again.
290290
`printf "FIXED_LOG_LITERALS=%s\\n" "$(grep -c '/tmp/sdui-dump-dev\\.log' ${JSON.stringify(SCRIPT)} || true)"`,
291+
// ── 5b. every mktemp template is portable: X's terminal, no suffix ───
292+
//
293+
// A spelling pin like 5 above, and for a defect from the same family: a
294+
// per-run temp path that is not actually per-run. `mktemp
295+
// "…dump-dev.XXXXXX.log"` reads as per-run and is per-run on GNU
296+
// coreutils, which splits the trailing `.log` off as an implied
297+
// `--suffix` and substitutes the X's anyway. That is a coreutils
298+
// extension. BSD mktemp(1) wraps mkstemp(3), which only ever replaces a
299+
// TRAILING run of X's, so on stock macOS nothing is substituted: the
300+
// first run creates the file called literally `sdui-dump-dev.XXXXXX.log`
301+
// and every later run dies there with `File exists`. Reported from a
302+
// real mac; measured here only at the libc layer, where glibc's
303+
// mkstemp("./x.XXXXXX.log") refuses with EINVAL outright.
304+
//
305+
// CI is Linux, so CI can never see this — which is precisely why it wants
306+
// a pin rather than a run. The counted shape is the ASSIGNMENT form, not
307+
// the word `mktemp`: the script's own comments quote the broken spelling
308+
// in order to explain it, and a pattern that read those would be red on
309+
// the fixed tree.
310+
//
311+
// TERMINAL_X is the vacuity guard, in the same spirit as
312+
// STEAL_CLAIM_ON_PICK above: SUFFIXED=0 is also what a file with no
313+
// mktemp templates left in it looks like, so the count of GOOD templates
314+
// is asserted beside the count of bad ones.
315+
`printf "SUFFIXED_MKTEMP_TEMPLATES=%s\\n" "$(grep -cE '^[[:space:]]*[A-Za-z_]+="\\$\\(mktemp "[^"]*X{3,}[^"X][^"]*"' ${JSON.stringify(SCRIPT)} || true)"`,
316+
`printf "TERMINAL_X_MKTEMP_TEMPLATES=%s\\n" "$(grep -cE '^[[:space:]]*[A-Za-z_]+="\\$\\(mktemp "[^"]*X{3,}"' ${JSON.stringify(SCRIPT)} || true)"`,
291317
'',
292318
'# ── 6. concurrent callers from ONE base get DISTINCT ports ──────────',
293319
// The card, as an executed assertion. Eight subshells, one base, at
@@ -424,4 +450,10 @@ describe.skipIf(!RUNNABLE)('gen-sdui-manifest.sh concurrent-run contract', () =>
424450
it('keeps no fixed dev-server log path (spelling pin)', () => {
425451
expect(seen.FIXED_LOG_LITERALS).toBe('0');
426452
});
453+
454+
it("keeps every mktemp template portable — X's terminal, no suffix (spelling pin)", () => {
455+
// Vacuity first: zero bad templates is also what zero templates looks like.
456+
expect(seen.TERMINAL_X_MKTEMP_TEMPLATES, JSON.stringify(seen)).toBe('3');
457+
expect(seen.SUFFIXED_MKTEMP_TEMPLATES, JSON.stringify(seen)).toBe('0');
458+
});
427459
});

scripts/gen-sdui-manifest.sh

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,26 @@ esac
568568
# started last, so a diagnosing reader could be reading another run's output —
569569
# and the failure branches below point readers straight at it. mktemp, matching
570570
# the pidfile beside it.
571-
DUMP_DEV_LOG="$(mktemp "${TMPDIR:-/tmp}/sdui-dump-dev.XXXXXX.log")"
571+
#
572+
# THE X'S ARE TERMINAL, AND NO SUFFIX FOLLOWS THEM — THE BSD/macOS FLOOR.
573+
# `mktemp "…dev.XXXXXX.log"` is a GNU coreutils spelling: coreutils splits the
574+
# trailing `.log` off as an implied `--suffix` (`--suffix=SUFF … is implied if
575+
# TEMPLATE does not end in X`, mktemp --help, coreutils 9.4) and substitutes the
576+
# X's anyway. BSD mktemp(1) is a thin wrapper over mkstemp(3), which takes the
577+
# template as-is and only ever replaces a TRAILING run of X's; measured here on
578+
# glibc, `mkstemp("./x.XXXXXX.log")` fails outright with EINVAL while
579+
# `mkstemp("./x.XXXXXX")` succeeds. On stock macOS the reported symptom is the
580+
# other branch of the same refusal: nothing is substituted, the literal file
581+
# `sdui-dump-dev.XXXXXX.log` is created on the first run, and every later run
582+
# dies at this line with `mkstemp failed on …: File exists` — the script
583+
# manufacturing its own blocking condition, one manual `rm` per pin bump.
584+
#
585+
# So: terminal X's, and the `.log` extension is simply given up. `--suffix` is
586+
# itself the GNU extension and is not the portable answer; a rename after the
587+
# fact would buy the extension back at the price of a second path and a window
588+
# in which the two disagree, and nothing reads these files by extension — they
589+
# are echoed to the operator and classified by content, never globbed.
590+
DUMP_DEV_LOG="$(mktemp "${TMPDIR:-/tmp}/sdui-dump-dev.XXXXXX")"
572591
DUMP_PID_FILE="$(mktemp "${TMPDIR:-/tmp}/sdui-dump-pid.XXXXXX")"
573592

574593
echo " dev server: port ${DUMP_PORT}, log ${DUMP_DEV_LOG}"
@@ -625,7 +644,9 @@ fi
625644
# The dump's combined output goes to a per-run file as well as to the terminal:
626645
# the failure branch classifies that text, and a remedy chosen from what
627646
# actually happened is the point (see sdui_dump_failure_advice above).
628-
DUMP_OUT_LOG="$(mktemp "${TMPDIR:-/tmp}/sdui-dump-out.XXXXXX.log")"
647+
# Terminal X's, no suffix after them — see the DUMP_DEV_LOG block above for the
648+
# BSD/macOS floor this shape is keeping.
649+
DUMP_OUT_LOG="$(mktemp "${TMPDIR:-/tmp}/sdui-dump-out.XXXXXX")"
629650

630651
# `${PIPESTATUS[0]}`, never `$?`: after a pipeline `$?` is TEE's status, and tee
631652
# does not fail, so `$?` here would read every failure of the dump as a success.

0 commit comments

Comments
 (0)