affaan-m/ECC · error · Error

Missing source file for repair: ${sourcePath || operation.so

Error message

Missing source file for repair: ${sourcePath || operation.sourceRelativePath}

What it means

During a 'copy-file' repair operation the installer resolves the original source path from the install-state and checks it exists. The install-state file is treated as attacker-controllable (per GHSA-hfpv-w6mp-5g95), so a missing or unresolvable source is a hard failure rather than a fallback. Thrown by executeRepairOperation when resolveOperationSourcePath returns null/empty or fs.existsSync is false.

Source

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

      repoVersion: context.packageVersion,
      manifestVersion: context.manifestVersion
    },
    lastValidatedAt: new Date().toISOString()
  };
}

function shouldRepairFromRecordedOperations(state) {
  return getManagedOperations(state).some(operation => operation.kind !== 'copy-file');
}

function executeRepairOperation(repoRoot, operation, trustedRoot) {
  // Install-state is attacker-controllable; never write/delete outside the
  // adapter-derived trusted root, regardless of what the state file claims
  // (GHSA-hfpv-w6mp-5g95).
  if (operation.kind === 'copy-file') {
    const sourcePath = resolveOperationSourcePath(repoRoot, operation);
    if (!sourcePath || !fs.existsSync(sourcePath)) {
      throw new Error(`Missing source file for repair: ${sourcePath || operation.sourceRelativePath}`);
    }

    copyContainedFile(sourcePath, operation.destinationPath, trustedRoot, 'repair');
    return operation.destinationPath;
  }

  if (operation.kind === 'render-template') {
    const renderedContent = getOperationTextContent(operation);
    if (renderedContent === null) {
      throw new Error(`Missing rendered content for repair: ${operation.destinationPath}`);
    }

    writeContainedFile(operation.destinationPath, renderedContent, trustedRoot, 'repair');
    return operation.destinationPath;
  }

  if (operation.kind === 'merge-json') {
    const payload = getOperationJsonPayload(operation);

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Verify the recorded source path exists: `ls <repoRoot>/<operation.sourceRelativePath>` (path is in the error message).
  2. Restore the missing file (checkout the branch/commit that owned it, or revert the prune).
  3. If the source was intentionally removed, uninstall first to clear the stale install-state, then re-install from the current repo state.
  4. Regenerate the install-state by performing a fresh install instead of repair.

Example fix

// before: repair fails — skills/foo/SKILL.md was deleted from repo
// after:
git checkout main -- skills/foo/SKILL.md
./install.sh --target claude --repair
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
const path = require('path');
function verifyOperationSources(repoRoot, operations) {
  const missing = [];
  for (const op of operations) {
    if (op.kind !== 'copy-file') continue;
    const src = op.sourceRelativePath ? path.join(repoRoot, op.sourceRelativePath) : null;
    if (!src || !fs.existsSync(src)) missing.push(src || op.sourceRelativePath);
  }
  return missing;
}
// before repair: assert verifyOperationSources(repoRoot, state.operations).length === 0

Type guard

function operationHasResolvableSource(repoRoot, op) {
  if (op.kind !== 'copy-file') return true;
  const src = op.sourceRelativePath ? path.join(repoRoot, op.sourceRelativePath) : null;
  return Boolean(src && fs.existsSync(src));
}

Try / catch

try {
  plan = createRepairPlanFromRecord(record, ctx);
} catch (err) {
  if (err.message.startsWith('Missing source file for repair')) {
    // restore the source from git, or uninstall + reinstall instead of repair
  } else throw err;
}

Prevention

When it happens

Trigger: Running repair (`./install.sh --repair` or the repair plan path) when the recorded source file (operation.sourceRelativePath under repoRoot) has been deleted, moved, or was never committed. Also when the install-state's sourceRelativePath was hand-edited to point at a non-existent file.

Common situations: Repo was pruned (skills/ or rules/ subdir removed) between install and repair; user switched branches and the recorded source no longer exists; partial clone missing the file; install-state file manually edited or corrupted.

Related errors


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