Skip to content

Shell: no mktemp, so agents improvise scratch paths they do not own #2267

Description

@trieloff

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

Activity

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

    cosmos-skippedSet by Backlog Dispatcherhuman-in-the-loopAt least one human has contributed to this threadskill issueFor issues that arrive with undeniable skill issue energy.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions