zeroclaw-labs/zeroclaw · error · anyhow::Error

config at {} is schema_version {from}; run `zeroclaw config

Error message

config at {} is schema_version {from}; run `zeroclaw config migrate` to update before modifying

What it means

ensure_disk_at_current_version also refuses configs older than V3, but unlike the newer-than-binary case this one is recoverable in place: it directs you to run `zeroclaw config migrate`. Modifying APIs are gated on this check so stale-format files are never partially edited by code that assumes the current layout.

Source

Thrown at crates/zeroclaw-config/src/migration.rs:753

            return Err(anyhow::Error::from(e)).with_context(|| {
                format!("failed to read config at {}", path.display().to_string())
            });
        }
    };
    let value: toml::Value =
        toml::from_str(&raw).context("failed to parse config TOML for version check")?;
    let from = detect_version(&value)?;
    if from == CURRENT_SCHEMA_VERSION {
        return Ok(());
    }
    if from > CURRENT_SCHEMA_VERSION {
        anyhow::bail!(
            "config at {} is schema_version {from}, newer than this binary supports ({})",
            path.display().to_string(),
            CURRENT_SCHEMA_VERSION,
        );
    }
    anyhow::bail!(
        "config at {} is schema_version {from}; run `zeroclaw config migrate` to update before modifying",
        path.display().to_string(),
    );
}

pub(crate) fn fold_string_into_array(
    table: &mut toml::Table,
    from_key: &str,
    to_key: &str,
) -> bool {
    let value = match table.remove(from_key) {
        Some(toml::Value::String(s)) if !s.is_empty() => s,
        Some(other) => {
            // Non-string: re-insert under from_key untouched (caller may handle).
            table.insert(from_key.to_string(), other);
            return false;
        }
        None => return false,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Run `zeroclaw config migrate` and let it walk the migration chain forward to V3.
  2. Verify the file afterwards: schema_version = 3 at the top of the TOML, then retry the original operation.
  3. Keep a backup before migrating (the chain is forward-only) in case a hand-edited field cannot be transformed.
  4. If migration fails, inspect the file for hand-edited fields the steps cannot handle, fix or remove them, and re-run migrate.

Example fix

// before
$ zeroclaw config set runtime.kind docker
Error: config at ~/.config/zeroclaw/config.toml is schema_version 2; run `zeroclaw config migrate` to update before modifying

// after
$ zeroclaw config migrate
$ zeroclaw config set runtime.kind docker   // succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

fn disk_version(raw: &str) -> Option<u32> {
    toml::from_str::<toml::Value>(raw)
        .ok()?
        .get("schema_version")?
        .as_integer()
        .map(|v| v as u32)
}

if let Some(v) = disk_version(&raw) {
    if v < 3 { /* run `zeroclaw config migrate` (with a backup) before modifying */ }
}

Try / catch

match ensure_disk_at_current_version(&path) {
    Err(e) if e.to_string().contains("zeroclaw config migrate") => {
        // back up the file, run the migrate command, then retry the original call once
    }
    other => other?,
}

Prevention

When it happens

Trigger: A config whose detected schema_version is 1 or 2 is passed to any config-modifying entry point routed through ensure_disk_at_current_version. This is exactly what you hit after upgrading the binary across a schema boundary and trying to change settings before migrating.

Common situations: Upgrading ZeroClaw across a schema bump and editing settings before migrating; restoring an old config backup into a new install; sharing a config written by an older release.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/f404a92aad607114. Report an issue: GitHub.