affaan-m/ECC · error · Error

Refusing to ${action}: missing destination path.

Error message

Refusing to ${action}: missing destination path.

What it means

Thrown by getManagedDestination() in scripts/lib/install-lifecycle.js as a safety guard when the destination path for a managed write is missing or not a string. The installer refuses to proceed with an empty destination because it cannot verify the write lands inside the trusted root.

Source

Thrown at scripts/lib/install-lifecycle.js:232

      return parseJsonLikeValue(operation[key], `${operation.kind}.${key}`);
    }
  }

  return undefined;
}

function formatJson(value) {
  return `${JSON.stringify(value, null, 2)}\n`;
}

function getManagedDestination(
  destinationPath,
  trustedRoot,
  action,
  { allowFinalSymlink = false } = {}
) {
  if (!destinationPath || typeof destinationPath !== 'string') {
    throw new Error(`Refusing to ${action}: missing destination path.`);
  }

  const canonicalRoot = assertWithinTrustedRoot(trustedRoot, trustedRoot, action);
  const resolvedDestination = path.resolve(destinationPath);
  const canonicalParent = assertWithinTrustedRoot(
    path.dirname(resolvedDestination),
    canonicalRoot,
    action
  );
  const managedPath = path.join(canonicalParent, path.basename(resolvedDestination));
  let stat = null;

  try {
    stat = fs.lstatSync(managedPath);
  } catch (error) {
    if (!error || (error.code !== 'ENOENT' && error.code !== 'ENOTDIR')) {
      throw error;
    }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Ensure every operation constructed has a non-empty string destinationPath.
  2. Validate the operation before dispatch: if (!op.destinationPath) throw new Error('destination required').
  3. Trace the operation builder (buildCopyFileOperation / merge op builders) to confirm destinationPath is computed from defined inputs.
  4. Default the destination explicitly when deriving from a config field that may be absent.

Example fix

// before
addFileCopyOperation(operations, {
  sourceRoot,
  sourceRelativePath: 'rules/x.md',
  destinationPath: options.dest, // undefined when options.dest missing
});

// after
if (!options.dest) throw new Error('options.dest is required');
addFileCopyOperation(operations, {
  sourceRoot,
  sourceRelativePath: 'rules/x.md',
  destinationPath: options.dest,
});
Defensive patterns

Strategy: validation

Validate before calling

function hasStringDestination(op) {
  return op && typeof op.destinationPath === 'string' && op.destinationPath.trim() !== '';
}
if (!hasStringDestination(operation)) {
  throw new Error(`Refusing to ${action}: missing destination path.`);
}

Type guard

function hasDestinationPath(op) {
  return op != null && typeof op === 'object'
    && typeof op.destinationPath === 'string'
    && op.destinationPath.trim() !== '';
}

Try / catch

try {
  return getManagedDestination(destinationPath, trustedRoot, action, opts);
} catch (err) {
  if (/missing destination path/.test(err.message)) {
    throw new Error(`Operation ${operation.kind} missing destinationPath`);
  }
  throw err;
}

Prevention

When it happens

Trigger: getManagedDestination(destinationPath, trustedRoot, action, opts) where destinationPath is undefined, null, '', or non-string. Reached from copy/merge/symlink operations that forgot to set destinationPath.

Common situations: Operation builder omitted destinationPath; a transform returned undefined for the destination; config has a blank path field; programmatic caller passed options.destinationPath from a missing config key.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/2dbed32ec08d7be5. Report an issue: GitHub.