{"record":{"id":"cf7bea07213fc3fc","repo":"can1357/oh-my-pi","slug":"invalid-findings-list-for-scanid","errorCode":null,"errorMessage":"Invalid findings list for ${scanId}","messagePattern":"Invalid findings list for (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/store.ts","lineNumber":323,"sourceCode":"\t\t\tif (plan) plans.push(plan);\n\t\t}\n\t\treturn plans;\n\t}\n\n\tasync getScan(scanId: string): Promise<SecurityScan | null> {\n\t\ttry {\n\t\t\treturn parseSecurityScan(await readJsonFile(path.join(this.#scanDirectory(scanId), \"scan.json\")));\n\t\t} catch (error) {\n\t\t\tif (isEnoent(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync #getBundleUnlocked(scanId: string): Promise<SecurityScanBundle | null> {\n\t\tconst scan = await this.getScan(scanId);\n\t\tif (!scan) return null;\n\t\tconst rawFindings = await readJsonFile(path.join(this.#scanDirectory(scanId), \"findings.json\"));\n\t\tif (!Array.isArray(rawFindings)) throw new Error(`Invalid findings list for ${scanId}`);\n\t\tconst findings = rawFindings.map(parseSecurityFinding);\n\t\tconst report = await readOptionalText(path.join(this.#scanDirectory(scanId), \"report.md\"));\n\t\tconst sarifText = await readOptionalText(path.join(this.#scanDirectory(scanId), \"results.sarif\"));\n\t\tconst bundle: SecurityScanBundle = { scan, findings };\n\t\tif (report !== undefined) bundle.report = report;\n\t\tif (sarifText !== undefined) bundle.sarif = JSON.parse(sarifText) as Record<string, unknown>;\n\t\treturn parseSecurityScanBundle(bundle);\n\t}\n\n\tasync getBundle(scanId: string): Promise<SecurityScanBundle | null> {\n\t\treturn withSecurityStoreWrite(this.#projectDirectory, () => this.#getBundleUnlocked(scanId));\n\t}\n\n\tasync listScans(): Promise<SecurityScanSummary[]> {\n\t\tconst index = await this.#readIndex();\n\t\tconst summaries: SecurityScanSummary[] = [];\n\t\tfor (const scanId of [...index.scanIds].reverse()) {\n\t\t\tconst bundle = await this.getBundle(scanId);","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/store.ts#L305-L341","documentation":"#getBundleUnlocked() reads scans/<scanId>/findings.json and requires it to be a JSON array. If the file exists but parses to anything else (object, string, null), the store declares the scan's findings corrupt and throws. This guards against truncated or tampered findings files being silently treated as zero findings.","triggerScenarios":"Calling getBundle(), getFinding(), listScans(), updateDisposition(), updateValidation(), or compare() for a scan whose findings.json on disk parses to a non-array — e.g. the file was overwritten with \"{}\" by an external tool or truncated during a non-atomic write.","commonSituations":"Manual editing or scripting against the store directory; an interrupted write from a tool that bypassed writeSecurityFileAtomic; restoring files from a partial backup; a disk-full event during a raw (non-atomic) write; someone replacing findings.json with a JSON object.","solutions":["Inspect scans/<scanId>/findings.json, fix it to be a JSON array of findings (restore from the producing scanner's output if available).","Re-run the scan and putBundle() a fresh, valid bundle to overwrite the corrupt findings file.","If the scan directory is unrecoverable, delete it and remove its id from index.json scanIds so it stops appearing.","Audit whatever wrote findings.json outside writeSecurityFileAtomic — all store writes must go through the atomic writer."],"exampleFix":"// before (findings.json)\n{ \"findings\": [...] }\n// after\n[ { \"id\": \"...\", ... }, ... ]  // top-level array","handlingStrategy":"validation","validationCode":"const raw = await Bun.file(path.join(store.projectDirectory, 'scans', scanId, 'findings.json')).text();\nif (!Array.isArray(JSON.parse(raw))) throw new Error(`corrupt findings for ${scanId}; re-scan required`);","typeGuard":"function isFindingsArray(v: unknown): v is unknown[] {\n  return Array.isArray(v);\n}","tryCatchPattern":"try {\n  const bundle = await store.getBundle(scanId);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Invalid findings list')) {\n    // re-run the scan and putBundle() a fresh bundle\n  } else throw err;\n}","preventionTips":["Never write findings.json with anything but the store's atomic writer (writeSecurityBundleToDirectory).","Keep findings.json a top-level JSON array; don't wrap it in an object.","Validate external/backup restores before dropping them into the store directory.","Re-run scans rather than hand-repairing findings files."],"tags":["security-store","corrupt-data","schema-validation"],"backgroundTag":"store-data-corrupt","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}