executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
troubleshooting-motoko-migrationsDeep reference for when the migration chain misbehaves — upgrade compatibility errors you cannot explain, migration files you cannot write, a project converted from legacy persistence, or a request to remove the chain. Load only when the rules in migrating-motoko-actors do not explain what you are seeing.
Independent GitHub snapshot · Automated classification · Original documentation governs
Reference for the failure modes of the mops-managed migration chain (migrations/).
Do not load this for routine work. Writing a migration, deciding implicit vs explicit, or naming a new file is covered by migrating-motoko-actors — that skill's rules are sufficient for the normal path. Come here when:
main.moOldActor and you are unsure whether that is a bug(with migration = ...)mops check complains about a state shape that is not in the current source.mo) in the canister's persistent metadata.Two consequences drive most of the rules in this skill:
IC0503, "missing migration var" class). Fix forward with a new migration instead.mops check validates your chain against the deployed canister's stable signature (its .most snapshot), not against local git history. So:
.most header identifies the persistence model: 1 classic, 3 legacy inline (with migration = ...), 4 enhanced migration chain. Once a project reaches 4 it never goes back (see EM is one-way).| Diagnostic | What it actually means | Action |
|---|---|---|
M0250 initialized stable field | A stable actor field has an inline initializer, which the chain (not the actor body) must supply | Reduce it to a typed declaration; move the initializer expression into the pending migration's NewActor |
M0254 / M0267 initial actor requires field | main.mo declares a stable field that no migration supplies. M0267 is the same problem when the deployed signature cannot account for it either | Add the field to the pending migration's NewActor with a value computed from OldActor or a well-defined default |
M0170 compatibility error | The new actor is not stable-compatible with the deployed signature | Add (or extend) the pending migration so the transformation is explicit |
M0255 stable signature downgrade | The build would move the canister from an enhanced chain back to an older persistence model — usually because the chain or the mops migrations config was removed | Restore what was removed; there is no supported downgrade |
| Chain start does not match the baseline | The first migration's OldActor does not describe the state the canister actually holds — the classic case is a converted project where the chain starts from the legacy shape, not {} | See Converted projects; do not rewrite OldActor to {} |
M0268 migration directory disagrees with deployed history | An already-applied migration was edited, deleted, or reordered, so the chain no longer matches the history recorded by the stable baseline. Surfaces at mops build (which folds the deployed baseline via --stable-baseline) and can pass mops check when check-limit trims that migration out | Restore the applied migration to exactly what was deployed and express further changes as a new migration. Only the oldest migrations may be trimmed; an applied migration in the middle cannot be removed |
M0278 file does not export a public migration function (warning M0251 on moc 1) | A file in the chain directory has no public func migration, so the chain skips it and its migration never runs | Name the function migration; move helper modules out of the chain directory |
M0014 effectful actor body (older toolchains) | Initialization work sitting in the actor body | Keep the actor body static — see Static actor body |
IC0503 missing migration var (runtime, after deploy) | A stable field never received a value — typically an edited or renamed applied migration, or a field added without a migration | Add a new migration that supplies the field; never repair by editing the old file |
IC0505 invalid custom section (install only, build and check pass) | The chain's stable-type history has outgrown the platform's custom-section limit | Nothing local fixes it — stop adding migrations and report it; see Why chains must stay short |
Write failures on a migration file are not a tooling bug — see the next section.
On platforms that freeze deployed migrations, each one is made read-only after a successful deploy and write tools reject it by its mode bits.
chmod it, delete it, rename it, or route around the write failure. The freeze encodes a runtime invariant, not a policy preference.Some enhanced-migration projects were not created that way — they were converted in place from legacy inline-migration persistence, with live data preserved. In such a project:
OldActor is the pre-conversion stable shape and whose NewActor passes the same data through unchanged. This is correct. Do not "simplify" it to OldActor = {} and do not re-seed the fields — that would discard live state.OldActor = {} is only right when the chain genuinely starts from an empty canister, i.e. the project used the chain from creation.{}. Never try to prune, flatten, or "clean up" the chain to make it start at {}.A project can also inherit such a chain without having been converted itself — forking or remixing a converted project copies the chain as-is, first migration included. The rule is the same either way, and it is readable straight off the chain: a non-empty OldActor in the earliest file means state predates the chain, so that file is correct as written.
Where a hosting platform owns the mops migrations config — the [canisters.<name>.migrations] section, its chain path, and check-limit — you cannot add, remove, or alter it, and the tooling will reject attempts.
check-limit to clear a "too many pending migrations" error. The fix is always to fold the extra changes into a single pending migration. Every file the error lists has not been applied yet, so any of them can be edited.chain = ... says. It is normally src/backend/migrations, but a project imported under a non-default canister name can differ. Read the mops config rather than assuming the path.Once a project uses the migration chain there is no supported path back to legacy persistence. Independent mechanisms enforce this:
M0255), and a legacy wasm deployed over an enhanced canister traps at runtime. Signature versions only move forward.So if a task asks you to delete the migrations/ directory, restore inline initializers on stable fields, or reintroduce (with migration = ...), say plainly that it is not possible for this project and offer the forward-only equivalent: a new migration that reshapes state toward what the user actually wants.
The one-migration-per-deployed-version rule is not bureaucratic. A long chain eventually becomes undeployable, and the failure is unrecoverable:
mops build and mops check still succeed at that point — only the install is rejected (IC0505, invalid custom section). A green check is not evidence that the chain is deployable, and no redeploy or retry recovers it: the oversized section is baked into the artifact..most signature serializes the whole chain.Nothing in the toolchain caps chain length, so the one-migration-per-version discipline is the only thing keeping a project away from that cliff: fold changes into the pending migration, keep stable types lean, and never add a migration whose body would just be old.
The actor body must not do work. Stable fields are typed declarations; their values come from the chain.
Older toolchains rejected any effectful actor body outright, and that compiler check has since been relaxed for transient bindings — but the rule stands regardless of whether the compiler complains, because top-level seeding re-runs on every upgrade and would overwrite live state.
Initialization belongs in exactly two places:
Do not create room to proceed by relaxing protected configuration, deleting or renaming history, or rewriting a frozen file. Those turn a build failure into data loss. If the chain and the deployed signature genuinely disagree in a way none of the above explains, stop and report the mismatch: which fields the checker names, what the latest migration's NewActor declares, and what main.mo declares.
migrating-motoko-actors — how to author migrations (the normal path)writing-motoko — language reference, actor and mixin rules, core library