denoland/deno · error · TypeError

ERR_UNKNOWN_SIGNAL

ERR_UNKNOWN_SIGNAL

Error message

Unknown signal: ${signal}

What it means

toDenoSignal() resolves a NUMERIC signal to a name by scanning os.signals (name → number). If no signal name maps to that number, ERR_UNKNOWN_SIGNAL is thrown with the number stringified. Only numbers present in the os.signals table (1–31-ish POSIX values like 9 for SIGKILL, 15 for SIGTERM) are accepted; 0 and out-of-range values like 99 are not.

Source

Thrown at ext/node/polyfills/internal/child_process.ts:1022

      return "null";
    case "inherit":
      return "inherit";
    case "ipc":
      return "ipc_for_internal_use";
    default:
      throw new ERR_INVALID_ARG_VALUE("stdio", pipe);
  }
}

function toDenoSignal(signal) {
  const nodeSignals = os.signals;
  if (typeof signal === "number") {
    for (const name of new SafeArrayIterator(keys(nodeSignals))) {
      if (nodeSignals[name] === signal) {
        return name;
      }
    }
    throw new ERR_UNKNOWN_SIGNAL(String(signal));
  }

  if (ReflectHas(nodeSignals, signal)) {
    return signal;
  }
  // On Windows, os.signals only lists native signals. Accept any
  // POSIX signal name so the caller can remap it to SIGTERM.
  if (isWindows && StringPrototypeStartsWith(signal, "SIG")) {
    return signal;
  }
  throw new ERR_UNKNOWN_SIGNAL(signal);
}

function keys(object) {
  return ObjectKeys(object);
}

function copyProcessEnvToEnv(

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Use named signals: child.kill('SIGTERM'), child.kill('SIGKILL')
  2. Derive numbers only from os.signals / os.constants.signals so they always resolve (e.g. os.constants.signals.SIGTERM)
  3. For liveness checks, test child.exitCode !== null or wrap kill() in try/catch instead of using signal 0

Example fix

// before
child.kill(0); // probe for liveness

// after
const alive = child.exitCode === null && child.signalCode === null;
// and when terminating:
child.kill('SIGTERM');
Defensive patterns

Strategy: validation

Validate before calling

import os from 'node:os';
function toSignalName(sig) {
  if (typeof sig === 'number') {
    const entry = Object.entries(os.signals).find(([, n]) => n === sig);
    if (!entry) throw new Error(`unsupported signal number ${sig}`);
    return entry[0];
  }
  return sig;
}

Type guard

import os from 'node:os';
const isValidSignalNumber = (n) =>
  typeof n === 'number' && Object.values(os.signals).includes(n);

Try / catch

try { child.kill(sig); } catch (err) {
  if (err?.code === 'ERR_UNKNOWN_SIGNAL') {
    child.kill('SIGTERM'); // safe portable default
  }
}

Prevention

When it happens

Trigger: child.kill(0) used as a liveness probe (signal 0 has no named entry); child.kill(99); signal values computed from arithmetic or read from config/protocol fields without validation.

Common situations: Porting Unix liveness checks that rely on kill(pid, 0) semantics; signal numbers forwarded from another service or IPC message; assuming any 1–64 number is valid because Linux accepts it at the syscall level.

Related errors


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