What is missing
mktemp does not exist in the shell. Neither a just-bash builtin nor a supplemental command:
$ command -v mktemp
$ mktemp -p /tmp/memory-curator
bash: mktemp: command not found
xxd, unlink, cmp, df and which all made it into packages/webapp/src/shell/supplemental-commands/; the one command whose whole job is «give me a path I am allowed to write» did not.
Why it is worth a supplemental command
Agents reach for mktemp reflexively, and its absence does not fail cleanly ⸺ it cascades. From the memory curator's 2026-08-18 14:02 pass, three consecutive turns:
S=$(mktemp) → bash: mktemp: command not found
sed '…' $F > $S → bash: : Is a directory # $S empty, redirect hits the cwd
sed '…' $F > /workspace/.s.txt → EACCES: sudo: approval denied '/workspace/.s.txt'
Command substitution swallows the first failure, so the agent does not learn that mktemp is missing ⸺ it learns that «> into a variable is a directory», concludes its scratch strategy is wrong, and improvises a path it does not own. The third line is a cone-mediated sudo prompt, which is the subject of #2164: an agent with no temp path of its own escalates into somebody else's tree. That pass was time-boxed at ten minutes and spent three turns on this.
The same shape shows up whenever a scoop needs an intermediate file. /tmp is not a safe guess: verified with throwaway scoops today, a /tmp write is denied both with writablePaths: [] and with an unrelated single-file grant (EACCES: sudo: approval denied '/tmp/filegrant-probe.js'), while an explicit ["/tmp/memory-curator/"] grant works.
Requested behaviour
Small surface, GNU-compatible where it costs nothing:
| form |
result |
mktemp |
create a unique empty file, print its absolute path |
mktemp -d |
same for a directory |
mktemp -p DIR / --tmpdir=DIR |
place it under DIR |
mktemp TEMPLATE (…XXXXXX) |
honour the template, ≥3 trailing X |
mktemp -u |
print a name without creating it |
The part that actually matters for this runtime is which directory is the default, and the answer cannot be a hardcoded /tmp:
- For the cone,
/tmp is writable and fine.
- For a scoop,
/tmp is out of grant unless someone granted it, so a mktemp that returns /tmp/tmp.abc123 hands back a path the caller cannot write. That is worse than the current error, because the failure moves one step later, into whatever wrote to the path.
Proposal: resolve the default as $TMPDIR when set, otherwise the caller's own temp root ⸺ /tmp for the cone, /scoops/<folder>/tmp for a scoop, which already exists in the scoop tree and falls inside the default writablePaths of /scoops/<folder>/. And set TMPDIR in the scoop's environment so the convention is discoverable: echo $TMPDIR prints empty today, in cone and scoop alike.
Two smaller asks:
Implementation pointers
packages/webapp/src/shell/supplemental-commands/mktemp-command.ts, modelled on unlink-command.ts / cmp-command.ts (both are small, FS-only, defineCommand from just-bash).
- Register in
packages/webapp/src/shell/supplemental-commands/index.ts, next to createUnlinkCommand().
- Test at
packages/webapp/tests/shell/supplemental-commands/mktemp-command.test.ts: unique names across calls, -d, template expansion, -p, default resolution for cone vs scoop, unknown flag exits non-zero.
Related
What is missing
mktempdoes not exist in the shell. Neither ajust-bashbuiltin nor a supplemental command:xxd,unlink,cmp,dfandwhichall made it intopackages/webapp/src/shell/supplemental-commands/; the one command whose whole job is «give me a path I am allowed to write» did not.Why it is worth a supplemental command
Agents reach for
mktempreflexively, and its absence does not fail cleanly ⸺ it cascades. From the memory curator's 2026-08-18 14:02 pass, three consecutive turns:Command substitution swallows the first failure, so the agent does not learn that
mktempis missing ⸺ it learns that «>into a variable is a directory», concludes its scratch strategy is wrong, and improvises a path it does not own. The third line is a cone-mediated sudo prompt, which is the subject of #2164: an agent with no temp path of its own escalates into somebody else's tree. That pass was time-boxed at ten minutes and spent three turns on this.The same shape shows up whenever a scoop needs an intermediate file.
/tmpis not a safe guess: verified with throwaway scoops today, a/tmpwrite is denied both withwritablePaths: []and with an unrelated single-file grant (EACCES: sudo: approval denied '/tmp/filegrant-probe.js'), while an explicit["/tmp/memory-curator/"]grant works.Requested behaviour
Small surface, GNU-compatible where it costs nothing:
mktempmktemp -dmktemp -p DIR/--tmpdir=DIRDIRmktemp TEMPLATE(…XXXXXX)Xmktemp -uThe part that actually matters for this runtime is which directory is the default, and the answer cannot be a hardcoded
/tmp:/tmpis writable and fine./tmpis out of grant unless someone granted it, so amktempthat returns/tmp/tmp.abc123hands back a path the caller cannot write. That is worse than the current error, because the failure moves one step later, into whatever wrote to the path.Proposal: resolve the default as
$TMPDIRwhen set, otherwise the caller's own temp root ⸺/tmpfor the cone,/scoops/<folder>/tmpfor a scoop, which already exists in the scoop tree and falls inside the defaultwritablePathsof/scoops/<folder>/. And setTMPDIRin the scoop's environment so the convention is discoverable:echo $TMPDIRprints empty today, in cone and scoop alike.Two smaller asks:
sprinkle) #2255. Amktempthat ignores--suffixand prints a path anyway would be a fresh source of silent breakage.man/commandsoutput so an agent can find it without guessing.Implementation pointers
packages/webapp/src/shell/supplemental-commands/mktemp-command.ts, modelled onunlink-command.ts/cmp-command.ts(both are small, FS-only,defineCommandfromjust-bash).packages/webapp/src/shell/supplemental-commands/index.ts, next tocreateUnlinkCommand().packages/webapp/tests/shell/supplemental-commands/mktemp-command.test.ts: unique names across calls,-d, template expansion,-p, default resolution for cone vs scoop, unknown flag exits non-zero.Related
mktempfailure above is its first domino.allowedCommandscan neither deny a base command nor supply a missing one. Listingmktempthere would have done nothing; the command has to exist first.sprinkle) #2255 ⸺ shell commands silently ignoring unknown flags.