{"record":{"id":"721d1e5e4c3784f2","repo":"can1357/oh-my-pi","slug":"invalid-security-store-plan-index-at-this-index","errorCode":null,"errorMessage":"Invalid security store plan index at ${this.#indexPath()}","messagePattern":"Invalid security store plan index at (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/store.ts","lineNumber":231,"sourceCode":"\t\t\t\tplanIds: [],\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t});\n\t\t}\n\t}\n\n\tasync #readIndex(): Promise<SecurityStoreIndex> {\n\t\tconst value = (await readJsonFile(this.#indexPath())) as Partial<SecurityStoreIndex>;\n\t\tif (value.schemaVersion !== STORE_SCHEMA_VERSION || value.projectKey !== this.#projectKey) {\n\t\t\tthrow new Error(`Unsupported security store index at ${this.#indexPath()}`);\n\t\t}\n\t\tif (!Array.isArray(value.scanIds) || !value.scanIds.every(id => typeof id === \"string\")) {\n\t\t\tthrow new Error(`Invalid security store scan index at ${this.#indexPath()}`);\n\t\t}\n\t\tif (\n\t\t\tvalue.planIds !== undefined &&\n\t\t\t(!Array.isArray(value.planIds) || !value.planIds.every(id => typeof id === \"string\"))\n\t\t) {\n\t\t\tthrow new Error(`Invalid security store plan index at ${this.#indexPath()}`);\n\t\t}\n\t\treturn { ...value, planIds: value.planIds ?? [] } as SecurityStoreIndex;\n\t}\n\n\tasync #writeIndex(index: SecurityStoreIndex): Promise<void> {\n\t\tawait writeSecurityFileAtomic(this.#indexPath(), `${JSON.stringify(index, null, 2)}\\n`);\n\t}\n\n\tasync #putBundleUnlocked(input: SecurityScanBundle): Promise<void> {\n\t\tconst bundle = parseSecurityScanBundle(input);\n\t\tif (bundle.scan.projectKey !== this.#projectKey) {\n\t\t\tthrow new Error(`Security scan project key ${bundle.scan.projectKey} does not match ${this.#projectKey}`);\n\t\t}\n\t\tconst scanDirectory = this.#scanDirectory(bundle.scan.id);\n\t\tawait ensurePrivateDirectory(scanDirectory);\n\t\tawait writeSecurityFileAtomic(\n\t\t\tpath.join(scanDirectory, \"findings.json\"),\n\t\t\t`${JSON.stringify(bundle.findings, null, 2)}\\n`,","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/store.ts#L213-L249","documentation":"SecurityStore.#readIndex() validates the store's index.json on every read. If the optional planIds field is present but is not an array of strings, the store considers the index corrupt and throws this error naming the index path. The store refuses to continue with a partially malformed index rather than silently dropping plan references.","triggerScenarios":"Calling any read-path API (index, storeDigest, listPlans, listScans, putBundle, putPlan, updateDisposition, updateValidation — anything reaching #ensureIndex/#readIndex) when the on-disk index.json has a planIds value that is not undefined and not an array of strings (e.g. planIds: \"secplan_abc\" or planIds: [1,2]).","commonSituations":"Manual hand-editing of index.json under the security state directory; an older store version writing a different planIds shape; a corrupted or partially written file restored from backup; a script rewriting the index and mistaking planIds for a string.","solutions":["Open the file named in the error and fix planIds to be an array of strings (or delete the key entirely — undefined planIds is accepted and defaults to []).","If the index is corrupt beyond repair, back it up and delete index.json; the next SecurityStore.open() recreates it via #ensureIndex (note: this drops the scan/plan id registry, scans remain on disk but unreferenced).","If the file was written by an older version, migrate it to schemaVersion 1 with valid scanIds/planIds arrays instead of hand-editing.","Check for concurrent writers or interrupted atomic renames that may have left a truncated index.json."],"exampleFix":"// before (index.json)\n{ \"schemaVersion\": 1, \"projectKey\": \"...\", \"repositoryRoot\": \"...\", \"scanIds\": [], \"planIds\": \"secplan_1\", \"updatedAt\": \"...\" }\n// after\n{ \"schemaVersion\": 1, \"projectKey\": \"...\", \"repositoryRoot\": \"...\", \"scanIds\": [], \"planIds\": [\"secplan_1\"], \"updatedAt\": \"...\" }","handlingStrategy":"validation","validationCode":"const idx = await Bun.file(path.join(store.projectDirectory, 'index.json')).json();\nif (idx.planIds !== undefined && !(Array.isArray(idx.planIds) && idx.planIds.every(id => typeof id === 'string'))) {\n  // repair or delete index.json before constructing the store\n}","typeGuard":"function hasValidPlanIds(v: unknown): v is { planIds?: string[] } {\n  const o = v as { planIds?: unknown };\n  return o.planIds === undefined || (Array.isArray(o.planIds) && o.planIds.every(id => typeof id === 'string'));\n}","tryCatchPattern":"try {\n  const plans = await store.listPlans();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('Invalid security store plan index')) {\n    await fs.rm(path.join(store.projectDirectory, 'index.json')); // recreated on next open\n  } else throw err;\n}","preventionTips":["Never hand-edit index.json; mutate the store only through putPlan/putBundle APIs.","Treat planIds as string[]|undefined in any external tooling that touches the store.","Back up the state directory before manual maintenance and validate JSON shape after restores.","Rely on the store's atomic writer — never write store files with raw fs.writeFile."],"tags":["security-store","corrupt-index","schema-validation"],"backgroundTag":"store-index-corrupt","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}