can1357/oh-my-pi · error · Error
Invalid security store plan index at ${this.#indexPath()}
Error message
Invalid security store plan index at ${this.#indexPath()} What it means
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.
Source
Thrown at packages/coding-agent/src/security/store.ts:231
planIds: [],
updatedAt: new Date().toISOString(),
});
}
}
async #readIndex(): Promise<SecurityStoreIndex> {
const value = (await readJsonFile(this.#indexPath())) as Partial<SecurityStoreIndex>;
if (value.schemaVersion !== STORE_SCHEMA_VERSION || value.projectKey !== this.#projectKey) {
throw new Error(`Unsupported security store index at ${this.#indexPath()}`);
}
if (!Array.isArray(value.scanIds) || !value.scanIds.every(id => typeof id === "string")) {
throw new Error(`Invalid security store scan index at ${this.#indexPath()}`);
}
if (
value.planIds !== undefined &&
(!Array.isArray(value.planIds) || !value.planIds.every(id => typeof id === "string"))
) {
throw new Error(`Invalid security store plan index at ${this.#indexPath()}`);
}
return { ...value, planIds: value.planIds ?? [] } as SecurityStoreIndex;
}
async #writeIndex(index: SecurityStoreIndex): Promise<void> {
await writeSecurityFileAtomic(this.#indexPath(), `${JSON.stringify(index, null, 2)}\n`);
}
async #putBundleUnlocked(input: SecurityScanBundle): Promise<void> {
const bundle = parseSecurityScanBundle(input);
if (bundle.scan.projectKey !== this.#projectKey) {
throw new Error(`Security scan project key ${bundle.scan.projectKey} does not match ${this.#projectKey}`);
}
const scanDirectory = this.#scanDirectory(bundle.scan.id);
await ensurePrivateDirectory(scanDirectory);
await writeSecurityFileAtomic(
path.join(scanDirectory, "findings.json"),
`${JSON.stringify(bundle.findings, null, 2)}\n`,View on GitHub (pinned to 9690622007)
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.
Example fix
// before (index.json)
{ "schemaVersion": 1, "projectKey": "...", "repositoryRoot": "...", "scanIds": [], "planIds": "secplan_1", "updatedAt": "..." }
// after
{ "schemaVersion": 1, "projectKey": "...", "repositoryRoot": "...", "scanIds": [], "planIds": ["secplan_1"], "updatedAt": "..." } Defensive patterns
Strategy: validation
Validate before calling
const idx = await Bun.file(path.join(store.projectDirectory, 'index.json')).json();
if (idx.planIds !== undefined && !(Array.isArray(idx.planIds) && idx.planIds.every(id => typeof id === 'string'))) {
// repair or delete index.json before constructing the store
} Type guard
function hasValidPlanIds(v: unknown): v is { planIds?: string[] } {
const o = v as { planIds?: unknown };
return o.planIds === undefined || (Array.isArray(o.planIds) && o.planIds.every(id => typeof id === 'string'));
} Try / catch
try {
const plans = await store.listPlans();
} catch (err) {
if (err instanceof Error && err.message.includes('Invalid security store plan index')) {
await fs.rm(path.join(store.projectDirectory, 'index.json')); // recreated on next open
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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]).
Common situations: 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.
Related errors
- Invalid findings list for ${scanId}
- Auth broker response failed schema validation
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE must contain a JSON object
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} must
- Kilo device authorization response missing required fields
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/721d1e5e4c3784f2.
Report an issue: GitHub.