can1357/oh-my-pi · error · Error

Invalid findings list for ${scanId}

Error message

Invalid findings list for ${scanId}

What it means

#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.

Source

Thrown at packages/coding-agent/src/security/store.ts:323

			if (plan) plans.push(plan);
		}
		return plans;
	}

	async getScan(scanId: string): Promise<SecurityScan | null> {
		try {
			return parseSecurityScan(await readJsonFile(path.join(this.#scanDirectory(scanId), "scan.json")));
		} catch (error) {
			if (isEnoent(error)) return null;
			throw error;
		}
	}

	async #getBundleUnlocked(scanId: string): Promise<SecurityScanBundle | null> {
		const scan = await this.getScan(scanId);
		if (!scan) return null;
		const rawFindings = await readJsonFile(path.join(this.#scanDirectory(scanId), "findings.json"));
		if (!Array.isArray(rawFindings)) throw new Error(`Invalid findings list for ${scanId}`);
		const findings = rawFindings.map(parseSecurityFinding);
		const report = await readOptionalText(path.join(this.#scanDirectory(scanId), "report.md"));
		const sarifText = await readOptionalText(path.join(this.#scanDirectory(scanId), "results.sarif"));
		const bundle: SecurityScanBundle = { scan, findings };
		if (report !== undefined) bundle.report = report;
		if (sarifText !== undefined) bundle.sarif = JSON.parse(sarifText) as Record<string, unknown>;
		return parseSecurityScanBundle(bundle);
	}

	async getBundle(scanId: string): Promise<SecurityScanBundle | null> {
		return withSecurityStoreWrite(this.#projectDirectory, () => this.#getBundleUnlocked(scanId));
	}

	async listScans(): Promise<SecurityScanSummary[]> {
		const index = await this.#readIndex();
		const summaries: SecurityScanSummary[] = [];
		for (const scanId of [...index.scanIds].reverse()) {
			const bundle = await this.getBundle(scanId);

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect scans/<scanId>/findings.json, fix it to be a JSON array of findings (restore from the producing scanner's output if available).
  2. Re-run the scan and putBundle() a fresh, valid bundle to overwrite the corrupt findings file.
  3. If the scan directory is unrecoverable, delete it and remove its id from index.json scanIds so it stops appearing.
  4. Audit whatever wrote findings.json outside writeSecurityFileAtomic — all store writes must go through the atomic writer.

Example fix

// before (findings.json)
{ "findings": [...] }
// after
[ { "id": "...", ... }, ... ]  // top-level array
Defensive patterns

Strategy: validation

Validate before calling

const raw = await Bun.file(path.join(store.projectDirectory, 'scans', scanId, 'findings.json')).text();
if (!Array.isArray(JSON.parse(raw))) throw new Error(`corrupt findings for ${scanId}; re-scan required`);

Type guard

function isFindingsArray(v: unknown): v is unknown[] {
  return Array.isArray(v);
}

Try / catch

try {
  const bundle = await store.getBundle(scanId);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Invalid findings list')) {
    // re-run the scan and putBundle() a fresh bundle
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/cf7bea07213fc3fc. Report an issue: GitHub.