mui/material-ui · error · Error
Failed to update package versions
Error message
Failed to update package versions
What it means
Thrown by setVersion() in scripts/canaryRelease.mts after Promise.allSettled() completes if any per-package task set hasError=true. Each individual failure (read/parse/write of a package's package.json) is already logged to stderr with its path and the underlying error before this aggregate is raised. It exists so the canary publish aborts rather than shipping packages with stale or partially-updated versions. The thrown message itself does not list which packages failed — that context only appears in the preceding console output.
Source
Thrown at scripts/canaryRelease.mts:168
const { stdout: commitTimestamp } = await $`git show --no-patch --format=%ct HEAD`;
const timestamp = formatDate(new Date(+commitTimestamp * 1000));
let hasError = false;
const tasks = packages.map(async (pkg) => {
const packageJsonPath = resolve(pkg.path, './package.json');
try {
const packageJson = JSON.parse(await readFile(packageJsonPath, { encoding: 'utf8' }));
packageJson.version = `${packageJson.version}-dev.${timestamp}-${currentRevisionSha}`;
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
} catch (error) {
console.error(`${chalk.red(`❌ ${packageJsonPath}`)}`, error);
hasError = true;
}
});
await Promise.allSettled(tasks);
if (hasError) {
throw new Error('Failed to update package versions');
}
}
function formatDate(date: Date) {
// yyyyMMdd-HHmmss
return date
.toISOString()
.replace(/[-:Z.]/g, '')
.replace('T', '-')
.slice(0, 15);
}
function buildPackages() {
if (process.env.CI) {
return $$`pnpm build:public:ci`;
}
return $$`pnpm build:public`;View on GitHub (pinned to bdc96df2cb)
Solutions
- Scroll up in the log: each failing path was already printed as `❌ <packageJsonPath>` together with the underlying error. Fix that file first.
- For each changed package, confirm the path from `pnpm list --recursive --filter ...[<baseline>] --depth -1 --only-projects --json` actually contains a valid package.json (e.g. `node -e "JSON.parse(require('fs').readFileSync('<path>/package.json','utf8'))"`).
- Restore a clean tree before re-running: `git restore .` (the script enforces a clean working directory at start via ensureCleanWorkingWindow).
- On CI, verify the runner has write access to the workspace and that the pnpm workspace catalog is up to date.
Example fix
// before
let hasError = false;
const tasks = packages.map(async (pkg) => {
const packageJsonPath = resolve(pkg.path, './package.json');
try {
/* ...read + bump + write... */
} catch (error) {
console.error(`${chalk.red(`❌ ${packageJsonPath}`)}`, error);
hasError = true;
}
});
await Promise.allSettled(tasks);
if (hasError) {
throw new Error('Failed to update package versions');
}
// after
const failed: string[] = [];
const tasks = packages.map(async (pkg) => {
const packageJsonPath = resolve(pkg.path, './package.json');
try {
/* ...read + bump + write... */
} catch (error) {
console.error(`${chalk.red(`❌ ${packageJsonPath}`)}`, error);
failed.push(packageJsonPath);
}
});
await Promise.allSettled(tasks);
if (failed.length > 0) {
throw new Error(`Failed to update package versions for: ${failed.join(', ')}`);
} Defensive patterns
Strategy: validation
Validate before calling
// Run before calling the canary release flow.
// Rejects early if any package's package.json is missing or unparseable,
// so setVersion() never reaches the aggregate throw.
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
async function assertPackagesWritable(packages: { path: string; name: string }[]) {
const failures: string[] = [];
await Promise.all(packages.map(async (pkg) => {
const p = resolve(pkg.path, 'package.json');
try {
const raw = await readFile(p, { encoding: 'utf8' });
const json = JSON.parse(raw);
if (typeof json.version !== 'string' || json.version.length === 0) {
failures.push(`${p}: missing or non-string "version"`);
}
} catch (err) {
failures.push(`${p}: ${(err as Error).message}`);
}
}));
if (failures.length > 0) {
throw new Error(`Pre-flight package.json check failed:\n - ${failures.join('\n - ')}`);
}
} Type guard
import type { PackageInfo } from './canaryRelease';
// Narrows a parsed package.json to one that setVersion() can safely mutate.
function isBumpablePackageJson(
value: unknown,
): value is { version: string; [k: string]: unknown } {
if (typeof value !== 'object' || value === null) return false;
const v = (value as { version?: unknown }).version;
return typeof v === 'string' && v.length > 0 && /^\d+\.\d+\.\d+/.test(v);
} Try / catch
// Callers of the release flow: catch the aggregate, then re-surface the
// already-printed per-package failures explicitly so CI logs are actionable.
try {
await setVersion(changedPackages);
} catch (err) {
if (err instanceof Error && err.message === 'Failed to update package versions') {
throw new Error(
'Aborted canary release: one or more package.json files could not be bumped. ' +
'See the per-package `❌ <path>` lines above for the underlying cause.',
);
}
throw err;
} Prevention
- Run the script from a clean working tree so ensureCleanWorkingWindow does not bail and partial state cannot accumulate.
- Keep the pnpm workspace catalog accurate: every package returned by `pnpm list --recursive --json` must point at a real, valid package.json.
- In CI, fail fast on `pnpm install` issues before invoking canary release, so a bad install does not masquerade as a bump failure.
- If you extend the per-package task, prefer collecting failed paths into an array and including them in the thrown message rather than relying solely on console output.
When it happens
Trigger: One of the parallel tasks in packages.map(...) rejected: readFile failed because pkg.path does not point at a real package.json; JSON.parse failed because the file is malformed or empty; packageJson.version was undefined and produced an invalid canary string; or writeFile failed due to permissions, read-only mount, or disk full. hasError flips to true in that task's catch block, then the post-allSettled guard throws.
Common situations: A newly added workspace whose path reported by `pnpm list --recursive --json` does not match where its package.json actually lives; a package.json left corrupt by a previous interrupted run (the script's own cleanUp does `git restore .` in finally, but a crash before finally leaves the tree dirty); CI runners with restricted write permissions on the workspace; an empty or hand-edited package.json committed by mistake.
Related errors
AI-assisted analysis of mui/material-ui@bdc96df2cb (2026-08-12).
Data as JSON: /api/errors/bbe1ad98f05881a2.
Report an issue: GitHub.