can1357/oh-my-pi · error · Error

invalid JSON: ${error instanceof Error ? error.message : Str

Error message

invalid JSON: ${error instanceof Error ? error.message : String(error)}

What it means

After a successful cargo metadata run, cargoWorkspacePackages() JSON.parses the captured stdout. If parsing fails it rethrows 'invalid JSON: <parse error>'. Since cargo was invoked with --format-version 1, this usually means the stdout was polluted by non-JSON output (warnings, progress lines) or a different cargo wrote human-readable text.

Source

Thrown at packages/coding-agent/src/cleanse/checkers.ts:500

		[cargo, "metadata", "--no-deps", "--format-version=1", "--manifest-path", manifest],
		{
			cwd: path.resolve(state.projectCwd, root),
			stderr: "full",
			allowNonZero: true,
		},
	);
	if (!result.ok) {
		throw new Error(
			sanitizeText(result.stderr || result.stdout)
				.replace(/\s+/g, " ")
				.trim(),
		);
	}
	let parsed: unknown;
	try {
		parsed = JSON.parse(result.stdout);
	} catch (error) {
		throw new Error(`invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
	}
	if (!isRecord(parsed)) throw new Error("metadata root is not an object");
	const workspaceMembers = new Set<string>();
	if (Array.isArray(parsed.workspace_members)) {
		for (const member of parsed.workspace_members) {
			if (typeof member === "string") workspaceMembers.add(member);
		}
	}
	const allowedFiles = new Set(state.files);
	const packages: string[] = [];
	if (!Array.isArray(parsed.packages)) return packages;
	for (const value of parsed.packages) {
		if (!isRecord(value) || typeof value.id !== "string" || !workspaceMembers.has(value.id)) continue;
		if (typeof value.name !== "string" || typeof value.manifest_path !== "string") continue;
		const relativeManifest = path.relative(state.projectCwd, value.manifest_path).split(path.sep).join("/");
		if (allowedFiles.has(relativeManifest)) packages.push(value.name);
	}
	return [...new Set(packages)].sort();

View on GitHub (pinned to 9690622007)

Solutions

  1. Run `cargo metadata --format-version 1` manually and confirm stdout is pure JSON
  2. Remove or bypass wrappers/shims that print to stdout before cargo's JSON
  3. Update cargo/rustup to a current version that honors --format-version
  4. If you own the code, parse only the first line that starts with '{' or capture with stderr fully separated
Defensive patterns

Strategy: validation

Validate before calling

function isCargoMetadataJson(stdout: string): boolean {
  try {
    const v: unknown = JSON.parse(stdout);
    return typeof v === "object" && v !== null && !Array.isArray(v);
  } catch { return false; }
}

Type guard

function isRecord(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  return await cargoWorkspacePackages(state);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('invalid JSON')) {
    logger.warn("cargo metadata produced non-JSON stdout; check cargo wrappers", {});
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: JSON.parse(result.stdout) throws because cargo printed extra non-JSON text to stdout (build scripts/progress), an old cargo ignores --format-version, or the captured stream mixed stderr into stdout.

Common situations: Custom cargo wrappers or .cargo/config printing banners; very old cargo versions; environment wrappers (e.g. cargo wrapped by sccache/Nix shims) emitting output before the JSON.

Understand the failure class

Related errors


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