{"record":{"id":"5ce4edb967a772f4","repo":"affaan-m/ECC","slug":"missing-source-file-for-repair-sourcepath-op","errorCode":null,"errorMessage":"Missing source file for repair: ${sourcePath || operation.sourceRelativePath}","messagePattern":"Missing source file for repair: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":601,"sourceCode":"      repoVersion: context.packageVersion,\n      manifestVersion: context.manifestVersion\n    },\n    lastValidatedAt: new Date().toISOString()\n  };\n}\n\nfunction shouldRepairFromRecordedOperations(state) {\n  return getManagedOperations(state).some(operation => operation.kind !== 'copy-file');\n}\n\nfunction executeRepairOperation(repoRoot, operation, trustedRoot) {\n  // Install-state is attacker-controllable; never write/delete outside the\n  // adapter-derived trusted root, regardless of what the state file claims\n  // (GHSA-hfpv-w6mp-5g95).\n  if (operation.kind === 'copy-file') {\n    const sourcePath = resolveOperationSourcePath(repoRoot, operation);\n    if (!sourcePath || !fs.existsSync(sourcePath)) {\n      throw new Error(`Missing source file for repair: ${sourcePath || operation.sourceRelativePath}`);\n    }\n\n    copyContainedFile(sourcePath, operation.destinationPath, trustedRoot, 'repair');\n    return operation.destinationPath;\n  }\n\n  if (operation.kind === 'render-template') {\n    const renderedContent = getOperationTextContent(operation);\n    if (renderedContent === null) {\n      throw new Error(`Missing rendered content for repair: ${operation.destinationPath}`);\n    }\n\n    writeContainedFile(operation.destinationPath, renderedContent, trustedRoot, 'repair');\n    return operation.destinationPath;\n  }\n\n  if (operation.kind === 'merge-json') {\n    const payload = getOperationJsonPayload(operation);","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install-lifecycle.js#L583-L619","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the recorded source path exists: `ls <repoRoot>/<operation.sourceRelativePath>` (path is in the error message).","Restore the missing file (checkout the branch/commit that owned it, or revert the prune).","If the source was intentionally removed, uninstall first to clear the stale install-state, then re-install from the current repo state.","Regenerate the install-state by performing a fresh install instead of repair."],"exampleFix":"// before: repair fails — skills/foo/SKILL.md was deleted from repo\n// after:\ngit checkout main -- skills/foo/SKILL.md\n./install.sh --target claude --repair","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nconst path = require('path');\nfunction verifyOperationSources(repoRoot, operations) {\n  const missing = [];\n  for (const op of operations) {\n    if (op.kind !== 'copy-file') continue;\n    const src = op.sourceRelativePath ? path.join(repoRoot, op.sourceRelativePath) : null;\n    if (!src || !fs.existsSync(src)) missing.push(src || op.sourceRelativePath);\n  }\n  return missing;\n}\n// before repair: assert verifyOperationSources(repoRoot, state.operations).length === 0","typeGuard":"function operationHasResolvableSource(repoRoot, op) {\n  if (op.kind !== 'copy-file') return true;\n  const src = op.sourceRelativePath ? path.join(repoRoot, op.sourceRelativePath) : null;\n  return Boolean(src && fs.existsSync(src));\n}","tryCatchPattern":"try {\n  plan = createRepairPlanFromRecord(record, ctx);\n} catch (err) {\n  if (err.message.startsWith('Missing source file for repair')) {\n    // restore the source from git, or uninstall + reinstall instead of repair\n  } else throw err;\n}","preventionTips":["Do not delete files from skills/, rules/, etc. that the installer copied without also uninstalling.","Pin the repo to the same commit used at install time before repairing.","Prefer re-install over repair when the source tree has changed substantially.","Keep install-state under version control or backed up."],"tags":["repair","install","filesystem","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}