{"record":{"id":"c18c20d5c657a718","repo":"affaan-m/ECC","slug":"refusing-install-install-state-target-does-not-match-the","errorCode":null,"errorMessage":"Refusing install: install-state target does not match the current plan at ${plan.installStatePath}.","messagePattern":"Refusing install: install-state target does not match the current plan at (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/ownership-guard.js","lineNumber":48,"sourceCode":" * which makes a later uninstall delete the user's file.\n *\n * This guard generalises the Claude flat-skill migration conflict pattern to\n * every adapter copy operation: when a destination exists and is NOT recorded\n * as an ECC-managed operation in the previous install-state, the operation is\n * skipped with a warning instead of overwriting and claiming ownership.\n *\n * All managed targets share this ownership boundary (#2964).\n */\nfunction prepareUserOwnedFileGuard(plan, migration) {\n  const previousState = pathExists(plan.installStatePath)\n    ? readInstallState(plan.installStatePath)\n    : null;\n  if (previousState && (\n    previousState.target.id !== plan.adapter.id\n    || comparablePath(previousState.target.root) !== comparablePath(plan.targetRoot)\n    || comparablePath(previousState.target.installStatePath) !== comparablePath(plan.installStatePath)\n  )) {\n    throw new Error(`Refusing install: install-state target does not match the current plan at ${plan.installStatePath}.`);\n  }\n  // Recorded files remain updateable by reinstall/repair. Preserve their prior\n  // digests if an attempt fails before writing them so uninstall detects drift.\n  const previousManagedOperations = new Map(\n    ((previousState && previousState.operations) || [])\n      .filter(operation => (\n        operation\n        && operation.ownership === 'managed'\n        && operation.destinationPath\n      ))\n      .map(operation => [comparablePath(operation.destinationPath), operation])\n  );\n  const managedDestinations = new Set(previousManagedOperations.keys());\n\n  const appliedOperations = [];\n  const skippedOperations = [];\n  const warnings = [];\n  for (const operation of (migration && migration.appliedOperations) || []) {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/ownership-guard.js#L30-L66","documentation":"prepareUserOwnedFileGuard reads the previous install-state file and verifies it describes the same target as the current install plan (adapter id, target root, and install-state path). If a previous state exists whose target differs from the current plan, the installer refuses rather than overwrite user-owned files recorded for a different destination. This protects against clobbering a different harness/project layout recorded earlier.","triggerScenarios":"Calling migration()/prepareUserOwnedFileGuard when the existing install-state at plan.installStatePath records previousState.target.id !== plan.adapter.id, a different targetRoot, or a different installStatePath — i.e., installing with a changed --target, a moved install root, or a relocated state file while old state persists.","commonSituations":"Switching from a global install to a project install (or vice versa) with stale state; renaming/moving the project directory after an install; upgrading across a version that changed the adapter id; pointing --target at a different harness while old install-state remains.","solutions":["Run the installer's uninstall/cleanup for the old target so the stale install-state is removed, then reinstall with the new plan.","Delete or move the stale install-state file at plan.installStatePath if you are sure its recorded files are no longer needed.","Re-run the install with the SAME target/root as the previous one so the plan matches recorded state.","If migrating intentionally, back up user-owned files first, clear state, then install to the new location."],"exampleFix":"// before: switching target while old state exists\nrunInstall({ target: 'claude-project', targetRoot: './new-dir' }); // throws\n\n// after: clear old state (or uninstall) first\nfs.rmSync(path.join('./new-dir', '.ecc-install-state.json'), { force: true });\nrunInstall({ target: 'claude-project', targetRoot: './new-dir' });","handlingStrategy":"validation","validationCode":"const prev = fs.existsSync(statePath) ? JSON.parse(fs.readFileSync(statePath, 'utf8')) : null;\nif (prev && (prev.target?.id !== plan.adapter.id || prev.target?.root !== plan.targetRoot)) {\n  // clear or migrate stale state before installing\n}","typeGuard":"function stateMatchesPlan(prev, plan) {\n  return !prev || (prev.target?.id === plan.adapter.id\n    && normPath(prev.target?.root) === normPath(plan.targetRoot)\n    && normPath(prev.target?.installStatePath) === normPath(plan.installStatePath));\n}","tryCatchPattern":"try {\n  await migration(plan);\n} catch (e) {\n  if (e.message.includes('install-state target does not match')) {\n    throw new Error(`Stale install state at ${plan.installStatePath}; run uninstall or remove the state file before switching targets.`, { cause: e });\n  }\n  throw e;\n}","preventionTips":["Always uninstall before changing --target or moving the install root.","Keep one install-state file per target and don't reuse directories across targets.","Check install-state content after version upgrades that may change adapter ids.","Back up user-owned files before clearing stale state."],"tags":["installer","state-mismatch","configuration"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}