{"record":{"id":"17600a1260c92aef","repo":"affaan-m/ECC","slug":"a-managed-install-state-path-is-required-before-preflight","errorCode":null,"errorMessage":"A managed install-state path is required before preflight.","messagePattern":"A managed install-state path is required before preflight\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/multi-harness-setup.js","lineNumber":348,"sourceCode":"  for (const [candidatePath, mode] of requirements) {\n    try {\n      accessSync(candidatePath, mode);\n    } catch (_error) {\n      const label = plan.target === 'kimi' ? 'Kimi' : 'Managed install';\n      throw new Error(\n        `${label} destination is not writable by the current user: ${candidatePath}. `\n        + 'Fix the project ownership or permissions, then retry.'\n      );\n    }\n  }\n}\n\nfunction preflightManagedPlan(plan, dependencies = {}) {\n  if (!plan || !Array.isArray(plan.operations)) {\n    throw new Error('A managed install plan with operations is required.');\n  }\n  if (typeof plan.installStatePath !== 'string' || plan.installStatePath.length === 0) {\n    throw new Error('A managed install-state path is required before preflight.');\n  }\n  const ownership = readOwnedDestinations(plan, dependencies);\n  const operations = plan.operations.map(operation => {\n    assertSafeInstallOperation(plan, operation);\n    return {\n      destinationPath: operation.destinationPath,\n      kind: operation.kind,\n      classification: classifyManagedOperation(operation, ownership.destinations),\n    };\n  });\n  assertManagedDestinationsWritable(plan, dependencies);\n  return {\n    plan,\n    operations,\n    ownershipSnapshot: {\n      destinations: [...ownership.destinations],\n      stateFingerprint: ownership.stateFingerprint,\n    },","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/multi-harness-setup.js#L330-L366","documentation":"preflightManagedPlan requires plan.installStatePath to be a non-empty string because the preflight step must read and compare an install-state file before applying operations. An empty or missing installStatePath means the plan cannot safely checkpoint state, so it throws before touching disk.","triggerScenarios":"Constructing a plan manually or mutating it so installStatePath is undefined/null/empty; a plan built by an older code path that didn't set installStatePath; a serialized plan that dropped the field.","commonSituations":"Custom tooling around multi-harness-setup building plans by hand; tests instantiating plan fixtures without installStatePath; upgrading plans created by a previous version of the script.","solutions":["Ensure the plan is built by createMultiHarnessPlan/applyMultiHarnessPlan which set installStatePath","If building manually, set plan.installStatePath to a valid file path before preflight","Check for empty-string paths coming from config/env interpolation"],"exampleFix":"// before\nconst plan = { operations: [...] };\n// after\nconst plan = { operations: [...], installStatePath: '.ecc/install-state.json' };","handlingStrategy":"validation","validationCode":"if (typeof plan?.installStatePath !== 'string' || plan.installStatePath.length === 0) {\n  throw new Error('plan.installStatePath must be a non-empty string');\n}","typeGuard":"function hasInstallStatePath(p) {\n  return typeof p?.installStatePath === 'string' && p.installStatePath.length > 0;\n}","tryCatchPattern":"try {\n  await preflight(plan);\n} catch (e) {\n  if (e.message.includes('install-state path is required')) {\n    plan = await createMultiHarnessPlan(request); // rebuild with proper state path\n  } else throw e;\n}","preventionTips":["Never strip installStatePath when serializing plans","Interpolate config paths carefully so empty strings don't reach the plan","Use the official plan builders instead of object literals"],"tags":["validation","install-state"],"backgroundTag":"missing-required-config-field","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"}