can1357/oh-my-pi · error · Error

metadata root is not an object

Error message

metadata root is not an object

What it means

cargo metadata can theoretically return JSON whose root is not an object (e.g. an array or scalar). The function validates with isRecord(parsed) and throws 'metadata root is not an object' before accessing parsed.workspace_members, defending the subsequent property access.

Source

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

			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. Verify what command actually produced the stdout (check for wrappers/shims on the cargo path)
  2. Run `cargo metadata --format-version 1 | jq type` — it should print '"object"'; if not, find the interfering tool
  3. If a wrapper emits a JSON array, fix the wrapper to emit the cargo metadata object
  4. Update cargo to a standard distribution version
Defensive patterns

Strategy: type-guard

Validate before calling

const parsed: unknown = JSON.parse(stdout);
if (!isRecord(parsed) || !Array.isArray(parsed.workspace_members)) {
  throw new Error("stdout is not a cargo metadata object");
}

Type guard

function isCargoMetadata(v: unknown): v is { workspace_members?: unknown[] } & 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 === "metadata root is not an object") {
    logger.warn("unexpected cargo metadata shape; skipping rust discovery", {});
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: JSON.parse succeeded but produced a non-object root — practically only when the captured stdout is valid JSON from something other than cargo metadata, or a mocked/stubbed cargo returns unexpected JSON.

Common situations: Test stubs or wrapper scripts emitting a JSON array/string; intercepted output from a different tool; extremely unusual cargo versions. Rarely seen with real cargo metadata, which always returns an object.

Related errors


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