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
- Make the filter fully synchronous: compute the answer with sync APIs (fs.statSync, string checks) and return a plain boolean
- If the filter must stay async, switch to the asynchronous fs.promises.cp()/fs.cp() which can await it
- 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
- Keep one sync filter and one async filter; never share across cp and cpSync
- Lint for 'async' keyword in filters passed to *Sync APIs
- Return an explicit boolean (not a truthy Promise) from sync filters
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.