Why is hs project migrate failing?
TL;DR — Identify your starting point first: 2025.2 apps just bump platformVersion and re-upload (no migrate command); 2023.1/2023.2/2025.1 project apps run hs project migrate; non-project legacy public apps run hs app migrate; legacy private apps can't migrate at all. The documented failure causes: a missing crm.objects.custom.read scope ("Build failed or timed out"), serverless environment variables that never migrate (re-add with hs secret add), invalid project config (hs project validate), and UID churn. CLI ≥ 8.4.0 throughout.
The mental model: three commands, one-way doors
"Migrating to 2026.03" is not one operation. Per HubSpot's migration guide, there are three distinct paths, and each tool refuses or misbehaves when pointed at the wrong starting state:
- Already on 2025.2 → there is no migrate step. Edit
platformVersionfrom2025.2to2026.03inhsproject.json, thenhs project upload. If you ran a migrate command here and got nothing useful, this is why. - Project app on 2023.1, 2023.2, or 2025.1 →
hs project migrate, run inside the project's working directory. It prompts per feature, then converts the whole project to 2026.03. - Legacy public app, no project →
hs app migrate(you may findhs project migrate-appin older tutorials — same operation, current command ishs app migrate). This creates a brand-new project around your live app: features become source files, the project builds and deploys as Build #1, and your original auth credentials, features, and installs are preserved. - Legacy private app → not currently supported. There is no migration path; plan a rebuild.
Before any of these: CLI 8.4.0 or newer (hs --version). Old CLIs fail these flows in undocumented ways, so rule that out first.
The phrase to internalize: one-way doors. After hs app migrate, project-defined features stop being editable in the app management UI and the old developer APIs. The escape hatch is narrower than people assume — Build #1 is a configuration baseline you can redeploy, not an un-migrate button.
The documented failure catalog
"Build failed or timed out. Inspect the failure to update your build and retry the migration." When app cards are involved, the documented cause is a missing crm.objects.custom.read scope on the legacy app. The fix sequence matters: add the scope to the legacy app, rebuild it on its current version (e.g. 2023.2), and only then retry the migration. Note the contained blast radius — your customer-facing app keeps working while the migration build fails; the scary status on the project details page refers to the new project only.
Serverless environment variables silently absent. hs project migrate does not carry over variables defined under environment in serverless.json. Nothing fails at migrate time — your functions just run without their config later. Re-create each variable with hs secret add immediately after migrating.
"Build failed: invalid configuration." Schema problems in the generated *-hsmeta.json files. Run hs project validate to get the actual field errors, and check that every function uid is unique across the project.
Features detaching after a later build. That's UID churn, not the migration. A feature's uid is its identity; change it in any subsequent build and the platform sees a new feature. Set UIDs deliberately during migration and treat them as immutable afterward.
CMS projects with serverless functions. The CMS 2025.2 migration explicitly does not handle serverless functions — that path is manual (and on the app platform, remember 2025.2 can't express functions at all).
Diagnose before you migrate, not after
The pattern across every failure above: the information you needed existed before the one-way command ran. That's the gap HS-X's migration tooling closes. Point it at your legacy project and it classifies every feature — what converts automatically, what needs human review, and why — without touching your app:
hs-x migrate inspect ./my-legacy-app # per-feature classification, no side effects
hs-x migrate report ./my-legacy-app # readiness summary + concrete next stepsinspect reads the project tree directly (hsproject.json, serverless.json, feature *-hsmeta.json files) and tells you up front about the things this page exists for — review-required features, serverless config that won't carry, cards that need replacement before the deadline. HS-X's broader migration model is built around the same principle: generate the new 2026.03 app as a separate testbed, verify it against a real portal, and only touch the app your users have installed once everything is proven.
If you're stuck right now
hs --version→ upgrade to ≥ 8.4.0 if older.- Confirm which of the four starting states you're in (the wrong-command failure mode).
hs project validatefor config errors; fix*-hsmeta.jsonfields and duplicate UIDs.- For "Build failed or timed out" with cards: add
crm.objects.custom.readto the legacy app, rebuild legacy, retry. - After any successful migrate: re-add serverless secrets (
hs secret add) before first deploy. - Keep Build #1 untouched as your revert baseline.