denoland/deno · error · FsError

too many temp dirs exist

Error message

too many temp dirs exist

What it means

Deno.makeTempDirSync() tries up to MAX_TRIES=10 times to create a directory named dir + prefix + 8-hex-digit random + suffix (tmp_name uses a random u64), retrying only when creation fails with AlreadyExists. If every attempt collides with an existing path, the op gives up with io::ErrorKind::AlreadyExists 'too many temp dirs exist', wrapped with the context 'tmpdir'.

Source

Thrown at ext/fs/ops.rs:1175

  for _ in 0..MAX_TRIES {
    let path = tmp_name(&mut rng, &dir, prefix.as_deref(), suffix.as_deref())?;
    // PERMISSIONS: this is ok because we verified the directory above
    let path = CheckedPath::unsafe_new(Cow::Owned(path));
    match fs.mkdir_sync(&path, false, Some(0o700)) {
      Ok(_) => {
        // PERMISSIONS: ensure the absolute path is not leaked
        let path =
          strip_dir_prefix(&dir, dir_arg.as_deref(), path.into_owned_path())?;
        return path_into_string(path.into_os_string());
      }
      Err(FsError::Io(ref e)) if e.kind() == io::ErrorKind::AlreadyExists => {
        continue;
      }
      Err(e) => return Err(e).context("tmpdir"),
    }
  }

  Err(FsError::Io(io::Error::new(
    io::ErrorKind::AlreadyExists,
    "too many temp dirs exist",
  )))
  .context("tmpdir")
}

#[op2(stack_trace)]
#[string]
pub async fn op_fs_make_temp_dir_async(
  state: Rc<RefCell<OpState>>,
  #[string] dir_arg: Option<String>,
  #[string] prefix: Option<String>,
  #[string] suffix: Option<String>,
) -> Result<String, FsOpsError> {
  let (dir, fs) =
    make_temp_check_async(state, dir_arg.as_deref(), "Deno.makeTempDir()")?;

  let mut rng = thread_rng();

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Clean stale files matching the prefix/suffix pattern out of the target directory
  2. Pass a distinct prefix or suffix so generated names stop colliding
  3. Point dir at a fresh empty directory, or omit dir to use the OS temp dir
  4. As a last resort, catch AlreadyExists and retry with a varying prefix

Example fix

// before
Deno.makeTempDirSync({ dir: './tmp', prefix: 'job-', suffix: '.tmp' });

// after
Deno.makeTempDirSync({ dir: './tmp', prefix: `job-${Date.now()}-` });
Defensive patterns

Strategy: retry

Validate before calling

function cleanTempCollisions(dir: string, prefix: string, suffix: string): void {
  try {
    for (const e of Deno.readDirSync(dir)) {
      const mid = e.name.slice(prefix.length, e.name.length - suffix.length);
      if (e.name.startsWith(prefix) && e.name.endsWith(suffix) && /^[0-9a-f]{8,}$/.test(mid)) {
        Deno.removeSync(`${dir}/${e.name}`, { recursive: true });
      }
    }
  } catch { /* directory may not exist yet */ }
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    const dir = Deno.makeTempDirSync({ prefix: `job-${attempt}-` });
    // use dir ...
    break;
  } catch (e) {
    if (!(e instanceof Error) || !/too many temp dirs exist/.test(e.message)) throw e;
  }
}

Prevention

When it happens

Trigger: Deno.makeTempDirSync({ dir, prefix, suffix }) where all 10 generated names already exist: a directory deliberately pre-populated with files matching the prefix/suffix pattern, a test stub of the filesystem that always returns AlreadyExists, or a fixed/seeded RNG in tests producing identical names.

Common situations: Unit tests mocking Deno filesystem ops to always throw AlreadyExists; an adversarial or sync-agent process squatting matching names; leftover cleanup scripts that pre-create the exact pattern. With a real 64-bit random name space, natural collisions are practically impossible — this almost always means a stubbed FS or deliberate name squatting.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/d2461911c2b1ef35. Report an issue: GitHub.