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
- Verify what command actually produced the stdout (check for wrappers/shims on the cargo path)
- Run `cargo metadata --format-version 1 | jq type` — it should print '"object"'; if not, find the interfering tool
- If a wrapper emits a JSON array, fix the wrapper to emit the cargo metadata object
- 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
- Validate JSON shape before consuming fields whenever parsing tool output
- Ensure only real cargo metadata output reaches the parser (no stubs/wrappers)
- Keep cargo versions standard so the schema stays an object
- Add smoke coverage for the Rust discovery path in CI
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
- invalid JSON: ${error instanceof Error ? error.message : Str
- err.to_string() (invalid glob pattern)
- err.to_string() (invalid regex pattern)
- err.to_string() (invalid exclude glob pattern)
- invalid {} argument: {}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8af19007a13ff985.
Report an issue: GitHub.