denoland/deno · error · NodeTypeError

ERR_INVALID_RETURN_VALUE

ERR_INVALID_RETURN_VALUE

Error message

Expected boolean to be returned from the "filter" function but got ${determineSpecificType(value)}.

What it means

fs.cpSync() requires its opts.filter callback to return a boolean synchronously. The polyfill explicitly checks isPromise(shouldCopy) and throws ERR_INVALID_RETURN_VALUE when the filter resolves asynchronously, because a synchronous copy loop cannot await a Promise. This matches Node.js behavior for the sync variant.

Source

Thrown at ext/node/polyfills/_fs/cp/cp_sync.ts:27

} = core.loadExtScript("ext:deno_node/internal/errors.ts");
const { op_node_cp_sync } = core.ops;
const { throwCpError } = core.loadExtScript("ext:deno_node/_fs/cp/cp.ts");

const {
  isPromise,
} = core;

function cpSyncFn(
  src,
  dest,
  opts,
) {
  try {
    if (opts.filter) {
      // deno-lint-ignore deno-internal/prefer-primordials
      const shouldCopy = opts.filter(src, dest);
      if (isPromise(shouldCopy)) {
        throw new ERR_INVALID_RETURN_VALUE("boolean", "filter", shouldCopy);
      }
      if (!shouldCopy) return;
    }

    op_node_cp_sync(
      src,
      dest,
      opts.dereference,
      opts.recursive,
      opts.force,
      opts.errorOnExist,
      opts.preserveTimestamps,
      opts.verbatimSymlinks,
      opts.mode ?? 0,
      opts.filter,
    );
  } catch (err) {
    if (typeof err?.os_errno === "number") {

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Make the filter fully synchronous: compute the answer with sync APIs (fs.statSync, string checks) and return a plain boolean
  2. If the filter must stay async, switch to the asynchronous fs.promises.cp()/fs.cp() which can await it
  3. If the filter awaits nothing real, remove the async keyword so it returns a boolean directly

Example fix

// before
fs.cpSync(src, dest, {
  filter: async (s, d) => (await fs.promises.stat(s)).size > 0,
});

// after
fs.cpSync(src, dest, {
  filter: (s) => fs.statSync(s).size > 0,
});
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof opts.filter === 'function') {
  const probe = opts.filter(src, dest);
  if (typeof probe.then === 'function') {
    throw new TypeError('cpSync filter must be synchronous');
  }
}
fs.cpSync(src, dest, opts);

Type guard

function isSyncFilter(filter) {
  const v = filter('/probe', '/probe');
  return typeof v === 'boolean' || (typeof v !== 'object' || v === null);
}

Try / catch

try { fs.cpSync(src, dest, { filter }); } catch (e) { if (e.code === 'ERR_INVALID_RETURN_VALUE' && /filter/.test(e.message)) { /* switch to fs.promises.cp */ } else throw e; }

Prevention

When it happens

Trigger: fs.cpSync(src, dest, { filter: async (s, d) => await someCheck(s) }); or any filter that implicitly returns a Promise (calls an async helper, returns a thenable).

Common situations: Sharing one filter implementation between fs.cp (async, tolerates async filters) and fs.cpSync; refactoring a filter to call a database or network lookup making it async; copying filter code from an async codebase into a sync CLI tool.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/1f3c1d18d03a5877. Report an issue: GitHub.