affaan-m/ECC · error

Refusing to apply install operation: missing destination…

Error message

Refusing to apply install operation: missing destination path.

What it means

assertSafeInstallOperation is the safety gate applied before every install operation; it first requires operation.destinationPath to be a non-empty string. A missing or non-string destination path means the plan is malformed and the installer refuses to apply it rather than writing to an unpredictable location. It then continues to check the path is within the trusted target root and not symlinked.

Solutions

  1. Inspect the failing operation in the plan and set its destinationPath to the absolute install path.
  2. Regenerate the plan via the adapter's planOperations instead of constructing operations manually.
  3. Check for a rename (destinationPath vs destRel/absoluteDestination) between plan producer and consumer versions.
  4. Filter/validate operations for a string destinationPath before calling applyInstallPlan.

Example fix

// before
applyInstallPlan(planWithOp({ sourceRel: 'a.md' }));
// after
applyInstallPlan(planWithOp({ sourceRel: 'a.md', destinationPath: path.join(targetRoot, 'a.md') }));
Defensive patterns

Strategy: type-guard

Validate before calling

const invalid = plan.operations.filter(op => !op || typeof op.destinationPath !== 'string');
if (invalid.length) throw new Error(`Plan has ${invalid.length} operations without destinationPath`);

Type guard

const hasDestinationPath = (op) => Boolean(op) && typeof op.destinationPath === 'string' && op.destinationPath.length > 0;

Try / catch

try {
  await applyInstallPlan(plan);
} catch (e) {
  if (e.message.includes('missing destination path')) {
    console.error('Plan is malformed: regenerate it via the adapter planner.');
  }
  throw e;
}

Prevention

When it happens

Trigger: An operation object without destinationPath (null/undefined/non-string) reaching assertSafeInstallOperation via applyInstallPlan, preflightClaudeSettingsOperations, or readOwnedDestinations — usually a plan produced by a broken adapter, partially constructed in tests, or corrupted when serialized/deserialized.

Common situations: Custom tooling generating plan JSON that omits destinationPath; an adapter's planOperations returning an operation missing the field after a refactor; tests hand-building operations with only destinationRel.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/ecbca2d764d8b29e. Report an issue: GitHub.

Appendix: source

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

  let changed = false;
  updateSettingsAtomic(settingsPath, latestSettings => {
    if (hasExplicitCommitAttributionPreference(latestSettings)) {
      return { settings: latestSettings };
    }
    changed = true;
    return { settings: withCommitAttributionDisabled(latestSettings) };
  }, options);
  return changed;
}

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 8321021c54)