denoland/deno · error · TypeError

ERR_WORKER_UNSUPPORTED_OPERATION

ERR_WORKER_UNSUPPORTED_OPERATION

Error message

Setting process.umask() is not supported in workers

What it means

Calling process.umask(mask) — the setting form — inside a worker_threads Worker throws ERR_WORKER_UNSUPPORTED_OPERATION, matching Node's own behavior: umask is process-global, so mutating it from a parallel thread is unsafe. The read-only form, process.umask() with no argument, is allowed in workers; under Deno both forms additionally require the allow-sys=umask permission.

Source

Thrown at ext/node/polyfills/process.ts:237

  // terminate_execution(). Unlike Node.js where reallyExit is a C++ binding
  // and a single nop() call suffices to trigger the stack guard check, in Deno
  // reallyExit goes through JS frames (Deno.exit -> exitHandler -> workerClose
  // -> op), so we need a loop back-edge for V8 to reliably detect the pending
  // termination and throw an uncatchable TerminationException.
  // On the main thread reallyExit() normally never returns, but users can
  // override it (test-process-really-exit.js), so only spin in workers.
  // ref: https://github.com/nodejs/node/blob/9cc7fcc26d/lib/internal/process/per_thread.js#L243-L251
  if (internals.__isWorkerThread) {
    // deno-lint-ignore no-empty
    for (;;) {}
  }
};

/** https://nodejs.org/api/process.html#processumaskmask */
export function umask(mask?: number | string): number {
  if (mask !== undefined) {
    if (internals.__isWorkerThread) {
      throw new ERR_WORKER_UNSUPPORTED_OPERATION("Setting process.umask()");
    }
    mask = parseFileMode(mask, "mask");
    return op_fs_umask(mask & 0o777);
  }
  // Note: reading the umask without setting has an inherent race condition
  // (two syscalls: set to 0 then restore). Node.js has the same issue and
  // has deprecated process.umask() with no arguments. In Deno, the underlying
  // op requires allow-sys=umask for both reading and setting.
  return op_fs_umask(null);
}

export const abort = () => {
  op_process_abort();
};

function addReadOnlyProcessAlias(
  name: string,
  option: string,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Set the umask once on the main thread before spawning workers
  2. Inside workers, only read: call process.umask() without an argument
  3. Guard the call: if (worker_threads.isMainThread) process.umask(mask)
  4. In Deno, run with --allow-sys=umask so the read path also works

Example fix

// before (runs inside a Worker)
process.umask(0o022); // throws ERR_WORKER_UNSUPPORTED_OPERATION

// after
const { isMainThread } = require("node:worker_threads");
if (isMainThread) process.umask(0o022);
// workers inherit the process-wide umask set by the main thread
Defensive patterns

Strategy: validation

Validate before calling

const { isMainThread } = require("node:worker_threads");
if (isMainThread) {
  process.umask(mask & 0o777); // set once, before spawning workers
} else {
  process.umask(); // read-only in workers
}

Type guard

const canSetUmask = () => require("node:worker_threads").isMainThread;

Try / catch

try {
  process.umask(0o022);
} catch (err) {
  if (err.code === "ERR_WORKER_UNSUPPORTED_OPERATION") {
    // skip — workers inherit the main thread's umask
  } else throw err;
}

Prevention

When it happens

Trigger: Code inside new Worker(...) calling process.umask(0o022); libraries like node-tmp that set umask at import time being loaded inside a worker pool; test runners (jest workers, etc.) executing setup files per worker.

Common situations: npm libraries that set umask on load running under Deno's node compat inside workers; deploy scripts assuming main-thread context; CI matrices where the same entrypoint sometimes runs in a worker.

Related errors


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