zeroclaw-labs/zeroclaw · error · anyhow::Error
cannot migrate backwards from V{from} to V{target}
Error message
cannot migrate backwards from V{from} to V{target} What it means
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.
Source
Thrown at crates/zeroclaw-config/src/migration.rs:826
v2.migrate().context("failed to migrate V2 → V3")
},
];
const _: () = assert!(
MIGRATION_STEPS.len() as u32 == CURRENT_SCHEMA_VERSION,
"MIGRATION_STEPS must have exactly one entry per schema version \
(length = CURRENT_SCHEMA_VERSION, including the slot-0 padding)",
);
/// Run the typed migration chain from `from` up to `CURRENT_SCHEMA_VERSION`.
/// `from` must be `< CURRENT_SCHEMA_VERSION` (caller checks).
fn run_chain(value: toml::Value, from: u32) -> Result<toml::Value> {
run_chain_until(value, from, CURRENT_SCHEMA_VERSION)
}
fn run_chain_until(value: toml::Value, from: u32, target: u32) -> Result<toml::Value> {
if target < from {
anyhow::bail!("cannot migrate backwards from V{from} to V{target}");
}
if target > CURRENT_SCHEMA_VERSION {
anyhow::bail!(
"target V{target} exceeds CURRENT_SCHEMA_VERSION (V{CURRENT_SCHEMA_VERSION})"
);
}
let mut cur = value;
for step in &MIGRATION_STEPS[from as usize..target as usize] {
cur = step(cur)?;
}
Ok(cur)
}
pub(crate) fn sync_table(doc: &mut toml_edit::Table, new: &toml::Table) {
// Drop keys not present in new
let to_remove: Vec<String> = doc
.iter()View on GitHub (pinned to 88bb9c8533)
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.
Example fix
// before
let migrated = run_chain_until(value, /* from */ 3, /* target */ 2)?; // cannot migrate backwards
// after: keep configs forward-only and validate the request
if target < from { anyhow::bail!("downgrade not supported"); }
let migrated = run_chain_until(value, from, CURRENT_SCHEMA_VERSION)?; Defensive patterns
Strategy: validation
Validate before calling
// before calling run_chain_until / generate
if target < from {
return Err(anyhow::anyhow!("downgrades unsupported: V{from} -> V{target}"));
}
if target > CURRENT_SCHEMA_VERSION {
return Err(anyhow::anyhow!("target above CURRENT_SCHEMA_VERSION"));
} Try / catch
match run_chain_until(value, from, target) {
Err(e) if e.to_string().contains("cannot migrate backwards") => {
// regenerate a default config for the older version instead
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- config at {} is schema_version {from}, newer than this binar
- purge_agent not supported by this memory backend
- config at {} is schema_version {from}; run `zeroclaw config
- target V{target} exceeds CURRENT_SCHEMA_VERSION (V{CURRENT_S
- Qdrant scroll failed ({status}): {text}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/a71be3a65b80b185.
Report an issue: GitHub.