affaan-m/ECC · critical · Error

Refusing to apply install operation: missing destination pat

Error message

Refusing to apply install operation: missing destination path.

What it means

Thrown by assertSafeInstallOperation in scripts/lib/install/apply.js. The guard is invoked twice per operation inside applyInstallPlan (before and after mkdirSync) and its first check is that operation is truthy and operation.destinationPath is a string. Without a destination path the installer cannot confine the write to a trusted root, so it refuses rather than guessing. This is a fail-closed security guard, not a normal validation error.

Source

Thrown at scripts/lib/install/apply.js:198

  return value;
}

function findHooksOperation(plan, hooksDestinationPath) {
  return plan.operations.find(item => (
    item.destinationPath === hooksDestinationPath
    && item.moduleId === 'hooks-runtime'
    && typeof item.sourcePath === 'string'
  ));
}

function isMcpConfigPath(filePath) {
  const basename = path.basename(String(filePath || ''));
  return basename === '.mcp.json' || basename === 'mcp.json';
}

function assertSafeInstallOperation(plan, operation) {
  if (!operation || typeof operation.destinationPath !== 'string') {
    throw new Error('Refusing to apply install operation: missing destination path.');
  }

  const targetRoot = plan && plan.targetRoot;
  assertWithinTrustedRoot(operation.destinationPath, targetRoot, 'install ECC file');

  const resolvedRoot = path.resolve(targetRoot);
  const resolvedTarget = path.resolve(operation.destinationPath);
  const relativePath = path.relative(resolvedRoot, resolvedTarget);
  const segments = relativePath ? relativePath.split(path.sep) : [];
  for (const segmentIndex of Array.from({ length: segments.length + 1 }, (_value, index) => index)) {
    const currentPath = segmentIndex === 0
      ? resolvedRoot
      : path.join(resolvedRoot, ...segments.slice(0, segmentIndex));
    try {
      const stats = fs.lstatSync(currentPath);
      if (stats.isSymbolicLink()) {
        throw new Error(
          `Refusing to install ECC file through symlinked path: '${currentPath}'.`

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Inspect plan.operations and confirm every entry has a non-empty string destinationPath.
  2. Build operations through createManagedOperation (helpers.js) so destinationPath is always set.
  3. If you are replaying an old install-state, regenerate the plan from the current ECC version instead of reusing the file.
  4. Add a unit test that asserts every emitted operation passes a destinationPath string check.

Example fix

// before
plan.operations.push({ kind: 'copy-file', sourcePath: src });

// after
plan.operations.push({
  kind: 'copy-file',
  sourcePath: src,
  destinationPath: path.join(targetRoot, 'file.txt'),
});
Defensive patterns

Strategy: validation

Validate before calling

function assertOperationsWellFormed(operations) {
  for (const op of operations) {
    if (!op || typeof op.destinationPath !== 'string' || op.destinationPath.length === 0) {
      throw new Error(`Operation missing destinationPath: ${JSON.stringify(op)}`);
    }
  }
}
assertOperationsWellFormed(plan.operations);

Type guard

function isInstallOperation(value) {
  return Boolean(
    value && typeof value === 'object'
    && typeof value.destinationPath === 'string'
    && typeof value.kind === 'string'
  );
}

Try / catch

try {
  applyInstallPlan(plan);
} catch (err) {
  if (/missing destination path/.test(err.message)) {
    console.error('Malformed operations:', plan.operations.filter(o => !o || typeof o.destinationPath !== 'string'));
  }
  throw err;
}

Prevention

When it happens

Trigger: A plan whose operations array contains a null entry, an object missing destinationPath, or an object whose destinationPath is undefined/a number/an empty value. Happens with hand-built operation objects or a stale install-state replayed under a renamed schema.

Common situations: Custom adapter that emits operations without going through createManagedOperation; an old install-state file whose field names no longer match; a programmatic plan assembled by hand without the destinationPath field.

Related errors


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