Hmbown/CodeWhale · error · anyhow::Error

config changed after it was loaded; reload {} and retry inst

Error message

config changed after it was loaded; reload {} and retry instead of overwriting concurrent changes

What it means

Config writes use optimistic concurrency: replace_config_document_if_unchanged() holds the write lock, re-reads the file, and requires it to still equal the snapshot the caller loaded. If the on-disk content differs from that expected snapshot (and is not already the desired body), the write is refused so concurrent edits are never overwritten; the caller must reload and retry.

Source

Thrown at crates/config/src/config_document.rs:101

/// acquired. This closes the `exists()`/create race in first-run writers.
pub fn create_config_document(path: &Path, body: &str) -> Result<()> {
    replace_config_document_if_unchanged(path, None, body)
}

/// Replace a full typed snapshot only when on-disk bytes still equal the
/// snapshot the caller originally loaded. `None` means the file was absent.
pub fn replace_config_document_if_unchanged(
    path: &Path,
    expected: Option<&str>,
    body: &str,
) -> Result<()> {
    with_config_write_lock(path, |path| {
        let current = read_optional_config(path)?;
        if current.as_deref() == Some(body) {
            return Ok(());
        }
        if current.as_deref() != expected {
            bail!(
                "config changed after it was loaded; reload {} and retry instead of overwriting concurrent changes",
                crate::quote_os_path(path)
            );
        }
        persist_locked(path, current.as_deref(), body.as_bytes())
    })
}

/// Set a value at `segments`, creating implicit parent tables while preserving
/// existing key/value decor.
pub fn set_config_document_value(
    doc: &mut toml_edit::DocumentMut,
    segments: &[&str],
    value: impl Into<toml_edit::Value>,
) -> Result<()> {
    let (key, parents) = segments
        .split_last()
        .context("config value path must not be empty")?;

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Reload config.toml and retry the same mutation — the standard recovery named in the message
  2. Close or pause the other Codewhale session that wrote config.toml before retrying
  3. Serialize your config edits (apply them from one process at a time) instead of racing concurrent writers
  4. If it recurs with a single process, check for external tools (sync agents, editors) rewriting the file underneath Codewhale

Example fix

// before (write once with a stale snapshot)
replace_config_document_if_unchanged(&path, Some(&loaded), &new_body)?; // bails if file changed

// after (reload and retry)
match replace_config_document_if_unchanged(&path, Some(&loaded), &new_body) {
    Err(_) if config_changed_on_disk(&path) => {
        let loaded = std::fs::read_to_string(&path)?; // reload
        replace_config_document_if_unchanged(&path, Some(&loaded), &new_body)?;
    }
    other => other?,
}
Defensive patterns

Strategy: retry

Validate before calling

// Compare the on-disk document with your snapshot immediately before writing:
let current = std::fs::read_to_string(&path).ok();
if current.as_deref() != Some(&loaded_snapshot) {
    // someone else wrote in between: reload now instead of failing later
    loaded_snapshot = current.unwrap_or_default();
    // re-apply your edit to the fresh snapshot before calling replace
}

Try / catch

// classic optimistic-lock retry loop:
for _ in 0..3 {
    let snapshot = read_optional_config(&path)?;
    let body = apply_edit(&snapshot, &edit)?;
    match replace_config_document_if_unchanged(&path, snapshot.as_deref(), &body) {
        Ok(()) => return Ok(()),
        Err(e) if e.to_string().contains("config changed after it was loaded") => continue,
        Err(e) => return Err(e),
    }
}
bail!("config kept changing; retry later")

Prevention

When it happens

Trigger: Two Codewhale processes (e.g. a TUI session and a CLI command, or two agents) load config.toml, both modify it, and the second one calls replace_config_document_if_unchanged() after the first already persisted — current != expected triggers the bail.

Common situations: Long-lived TUI session open while a background agent or second terminal mutates settings; scripted config edits racing an interactive session; editors with auto-save touching config.toml between load and write.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/ddf1b42118beb8a1. Report an issue: GitHub.