pnpm/pnpm · error
Custom resolver returned invalid resolution: {err}
Error message
Custom resolver returned invalid resolution: {err} What it means
The custom resolver did return a `resolution` value, but `serde_json::from_value` could not deserialize it into pnpm's Resolution type — the object's fields do not match the expected resolution shape (tarball/dir/integrity keys and their types). The appended serde error names the offending field so the mismatch is visible.
Source
Thrown at pnpm/crates/hooks/src/custom_resolver_adapter.rs:128
let id = result.get("id").and_then(Value::as_str).ok_or_else(|| {
let err: ResolveError = Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Custom resolver did not return an 'id' field",
));
err
})?;
let resolution_val = result.get("resolution").ok_or_else(|| {
let err: ResolveError = Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Custom resolver did not return a 'resolution' field",
));
err
})?;
let resolution = serde_json::from_value(resolution_val.clone()).map_err(|err| {
let resolve_err: ResolveError = Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Custom resolver returned invalid resolution: {err}"),
));
resolve_err
})?;
// The hook's whole result is carried through, so a manifest
// the resolver returns must survive — without it the installer
// would re-fetch the tarball just to read `package.json`.
let manifest = match result.get("manifest") {
Some(manifest_val) => {
Some(Arc::new(serde_json::from_value(manifest_val.clone()).map_err(|err| {
let resolve_err: ResolveError = Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Custom resolver returned invalid manifest: {err}"),
));
resolve_err
})?))View on GitHub (pinned to 6261b7f388)
Solutions
- Read the appended serde error — it identifies the failing field and expected type
- Return the canonical resolution shape for the dependency type (`tarball`, `dir`, `integrity`, etc.)
- Cross-check the field names against a lockfile entry produced by a real dependency of the same protocol
- Keep `resolution` minimal — only the documented keys for that resolver type
Example fix
// before
{ id: 'myproto:foo@1.0.0', resolution: { url: 'https://example.com/foo.tgz' } }
// after
{ id: 'myproto:foo@1.0.0', resolution: { tarball: 'https://example.com/foo.tgz' } } Defensive patterns
Strategy: validation
Validate before calling
// JS: validate the resolution shape for a tarball dependency before returning
const REQUIRED = { tarball: 'string' }
for (const [key, type] of Object.entries(REQUIRED)) {
if (typeof result.resolution[key] !== type) {
throw new Error(`resolution.${key} must be ${type}`)
}
} Type guard
/** @returns {resolution is { tarball: string }} */
function isTarballResolution(resolution) {
return resolution != null && typeof resolution.tarball === 'string'
} Prevention
- Model resolution variants in TypeScript (tarball/dir/integrity) so wrong shapes fail at compile time
- Keep only documented resolution keys; extra/renamed fields trip deserialization
- Test hook returns against pnpm's current resolution schema on every pnpm upgrade
When it happens
Trigger: `resolve()` returns `resolution` with wrong field names or types: `{ url }` instead of `{ tarball }`, strings where records are expected, or extra fields that fail deserialization for that dependency type.
Common situations: Typos in resolution keys; resolvers written against a different pnpm version's resolution schema; passing whole registry metadata as the resolution.
Related errors
- Custom resolver did not return an 'id' field
- Custom resolver did not return a 'resolution' field
- Custom resolver returned invalid manifest: {err}
- CONFIG_SET_EMPTY_KEY
- CONFIG_SET_DEEP_KEY
AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17).
Data as JSON: /api/errors/346f08d0cd5f322c.
Report an issue: GitHub.