Skip to content

create-skill: mandated mktemp template is broken on macOS (BSD mktemp ignores non-trailing X's) #2157

Description

@carlos-alm

Problem

.claude/skills/create-skill/SKILL.md mandates this temp-file pattern (Pattern 3, Pattern 4, Pattern 13, and the Phase 4 checklist):

mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX.ext"

Pattern 13 explicitly justifies it as portable: "Use mktemp with template syntax (mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX.ext") — GNU flags like --suffix and -p are not available on macOS BSD mktemp".

On macOS this does not work. BSD mktemp only substitutes the X placeholders when they are trailing. With a suffix after them it returns the template literally and creates a file with that literal name:

$ mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX.txt"
/var/folders/.../T//tmp.XXXXXXXXXX.txt      # <-- not randomized

$ mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX.json"   # second run, file now exists
mktemp: mkstemp failed on /var/folders/.../T//tmp.XXXXXXXXXX.json: File exists

$ mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"        # trailing X's
/var/folders/.../T//tmp.a4gpeJrPkl          # correctly randomized

Impact

Every skill generated by /create-skill that follows the mandated pattern gets a fixed, non-unique temp path on macOS. This produces exactly the failure Pattern 4 exists to prevent:

  • Concurrent sessions collide on the same literal path — a real risk given this repo's parallel-worktree workflow.
  • Re-runs fail outright with mkstemp failed ... File exists once the literal file is left behind by an earlier run.
  • Failures are silent until the second run, so they look like flakes.

The pattern is currently reproduced in the guidance for Patterns 2, 3, 4, 13, 14, and Phase 5, so generated skills inherit it consistently.

Suggested fix

Use trailing Xs, and when an extension is genuinely required (e.g. codegraph's extension-based language detection, which is the stated reason for the suffix), create a temp directory and place the named file inside it:

# No extension needed:
TMP=$(mktemp "${TMPDIR:-/tmp}/myskill.XXXXXXXXXX")

# Extension required (codegraph language detection):
TMPDIR_WORK=$(mktemp -d "${TMPDIR:-/tmp}/myskill.XXXXXXXXXX")
trap 'rm -rf "$TMPDIR_WORK"' EXIT
PREV_FILE="$TMPDIR_WORK/prev.js"

Also worth adding a lint-skill.sh check that flags mktemp templates whose X run is not trailing — it would have caught this.

Notes

  • lint-skill.sh itself uses the broken form (mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX.blocks"), so the linter can fail on a re-run if the literal file survives.
  • Found while authoring the /fixer skill; that skill uses the trailing-X form instead.
  • Platform: macOS (Darwin 25.2.0), BSD mktemp. GNU coreutils mktemp does substitute non-trailing X's, which is likely why this went unnoticed on CI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    follow-upDeferred work from PR reviews that needs tracking

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions