vercel/turborepo · error · Error
Invalid version
Error message
Invalid version: ${version} What it means
During migration, the update-versioned-schema-json transform builds the new schema.json URL from the resolved turbo version. It parses the version with semver.parse to keep prerelease parts, and falls back to semver.coerce for loose strings. It throws this Error only when both fail, which means the version is not recognizable as semver at all.
Solutions
- Check the version you passed with --to and correct it to a semver string such as 2.9.4 or 2.9.4-canary.5
- Inspect your package.json for a malformed turbo version and fix it to a real published version
- Re-run the codemod after correcting the source of the version string
Example fix
// before
getVersionedSchemaUrl("main");
// after
getVersionedSchemaUrl("2.9.4-canary.5"); // "https://2-9-4-canary-5.turborepo.dev/schema.json" Defensive patterns
Strategy: type-guard
Validate before calling
import semver from "semver";
// accept only versions that parse or coerce before generating schema URLs
function assertResolvableVersion(version: string): void {
if (!semver.parse(version) && !semver.coerce(version)) {
throw new Error(`Invalid version: ${version}`);
}
} Type guard
import semver from "semver";
function isResolvableSemver(version: string): boolean {
return semver.parse(version) !== null || semver.coerce(version) !== null;
} Prevention
- Validate version strings with semver.valid or semver.coerce before use
- Source versions from package.json or the registry, never free-text input
- Reject branch names and commit SHAs in version fields during code review
When it happens
Trigger: getVersionedSchemaUrl receives a string with no leading numeric version, such as "banana", "v", or "". semver.parse returns null and semver.coerce also returns null, so resolved is undefined and it throws.
Common situations: A package.json or lockfile contains a corrupted turbo version. A script passes a branch name or arbitrary string where a version belongs. An empty version string after a failed earlier step.
Related errors
- turbo@ does not exist
- [33m"turbo-ignore" is deprecated. Use "turbo query…
- [33mLearn more…
- Could not find turbo in
- Found catalog reference but
AI-assisted analysis of vercel/turborepo@f9245100cf (2026-08-17).
Data as JSON: /api/errors/e56dadd4700ede9b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/turbo-codemod/src/transforms/update-versioned-schema-json.ts:104
* Replaces dots with hyphens to create a valid DNS subdomain.
* e.g., "2.7.5" -> "v2-7-5", "2.9.4-canary.5" -> "v2-9-4-canary-5"
*/
function versionToSubdomain(version: string): string {
return `v${version.replaceAll(".", "-")}`;
}
/**
* Generates the new versioned schema URL.
* Uses semver.parse to preserve prerelease identifiers (e.g., canary.5),
* falling back to semver.coerce for non-standard version strings.
*/
export function getVersionedSchemaUrl(version: string): string {
// parse preserves prerelease: "2.9.4-canary.5" -> "2.9.4-canary.5"
// coerce strips it: "2.9.4-canary.5" -> "2.9.4"
const parsed = parse(version);
const resolved = parsed ? parsed.version : coerce(version)?.version;
if (!resolved) {
throw new Error(`Invalid version: ${version}`);
}
const subdomain = versionToSubdomain(resolved);
return `https://${subdomain}.turborepo.dev/schema.json`;
}
/**
* Updates any old schema URLs in file content to the new versioned URL
*/
function updateSchemaUrls(content: string, newUrl: string): string {
let updated = content;
// Replace static old URLs
for (const oldUrl of OLD_SCHEMA_URLS) {
updated = updated.replaceAll(oldUrl, newUrl);
}
// Replace outdated versioned URLs (e.g., v2-7-4 -> v2-7-5)
updated = updated.replaceAll(VERSIONED_SCHEMA_URL_REGEX, newUrl);
return updated;
}View on GitHub (pinned to f9245100cf)