{"record":{"id":"a71be3a65b80b185","repo":"zeroclaw-labs/zeroclaw","slug":"cannot-migrate-backwards-from-v-from-to-v-target","errorCode":null,"errorMessage":"cannot migrate backwards from V{from} to V{target}","messagePattern":"cannot migrate backwards from V(.+?) to V(.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/migration.rs","lineNumber":826,"sourceCode":"        v2.migrate().context(\"failed to migrate V2 → V3\")\n    },\n];\n\nconst _: () = assert!(\n    MIGRATION_STEPS.len() as u32 == CURRENT_SCHEMA_VERSION,\n    \"MIGRATION_STEPS must have exactly one entry per schema version \\\n     (length = CURRENT_SCHEMA_VERSION, including the slot-0 padding)\",\n);\n\n/// Run the typed migration chain from `from` up to `CURRENT_SCHEMA_VERSION`.\n/// `from` must be `< CURRENT_SCHEMA_VERSION` (caller checks).\nfn run_chain(value: toml::Value, from: u32) -> Result<toml::Value> {\n    run_chain_until(value, from, CURRENT_SCHEMA_VERSION)\n}\n\nfn run_chain_until(value: toml::Value, from: u32, target: u32) -> Result<toml::Value> {\n    if target < from {\n        anyhow::bail!(\"cannot migrate backwards from V{from} to V{target}\");\n    }\n    if target > CURRENT_SCHEMA_VERSION {\n        anyhow::bail!(\n            \"target V{target} exceeds CURRENT_SCHEMA_VERSION (V{CURRENT_SCHEMA_VERSION})\"\n        );\n    }\n\n    let mut cur = value;\n    for step in &MIGRATION_STEPS[from as usize..target as usize] {\n        cur = step(cur)?;\n    }\n    Ok(cur)\n}\n\npub(crate) fn sync_table(doc: &mut toml_edit::Table, new: &toml::Table) {\n    // Drop keys not present in new\n    let to_remove: Vec<String> = doc\n        .iter()","sourceCodeStart":808,"sourceCodeEnd":844,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/migration.rs#L808-L844","documentation":"run_chain_until only applies forward migration steps (it slices MIGRATION_STEPS[from..target]), so it bails immediately when the requested target is lower than the source version. Downgrades are unsupported because individual steps are not reversible.","triggerScenarios":"Calling run_chain_until(value, from, target) with target < from — for example generate() targeting V2 from a V3 document, or run_chain invoked after detect_version returned a from above the requested target.","commonSituations":"Tooling that tries to produce a config for an older zeroclaw by asking the migration engine to run backwards; a version parsed from user input or config forwarded into the target without clamping.","solutions":["Do not request a downgrade; target CURRENT_SCHEMA_VERSION and keep configs forward-only.","If you truly need an old-format config, generate a default config for that version and copy values over by hand.","Restore a pre-upgrade backup of the config instead of migrating back.","Clamp and validate the target argument before calling so user input can never express target < from."],"exampleFix":"// before\nlet migrated = run_chain_until(value, /* from */ 3, /* target */ 2)?; // cannot migrate backwards\n\n// after: keep configs forward-only and validate the request\nif target < from { anyhow::bail!(\"downgrade not supported\"); }\nlet migrated = run_chain_until(value, from, CURRENT_SCHEMA_VERSION)?;","handlingStrategy":"validation","validationCode":"// before calling run_chain_until / generate\nif target < from {\n    return Err(anyhow::anyhow!(\"downgrades unsupported: V{from} -> V{target}\"));\n}\nif target > CURRENT_SCHEMA_VERSION {\n    return Err(anyhow::anyhow!(\"target above CURRENT_SCHEMA_VERSION\"));\n}","typeGuard":null,"tryCatchPattern":"match run_chain_until(value, from, target) {\n    Err(e) if e.to_string().contains(\"cannot migrate backwards\") => {\n        // regenerate a default config for the older version instead\n    }\n    other => other?,\n}","preventionTips":["Treat configs as forward-only; never build tooling that downgrades them.","Clamp user-supplied target versions to [from, CURRENT_SCHEMA_VERSION] before calling.","Keep pre-upgrade backups so rollback means restoring a file, not reverse migration."],"tags":["migration","downgrade","versioning"],"backgroundTag":"migration-downgrade-unsupported","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}