Hmbown/CodeWhale · error
config changed after it was loaded; reload
Error message
config changed after it was loaded; reload {} and retry instead of overwriting concurrent changes What it means
Optimistic-concurrency guard in replace_config_document_if_unchanged. Under the config write lock the file is re-read and compared against the `expected` bytes the caller loaded earlier; if the on-disk content differs, another writer changed the config and this save is refused instead of silently overwriting the concurrent change.
Solutions
- Reload the config document from disk and re-apply your change, then save again.
- Merge your intended edits with the concurrent changes before retrying.
- Avoid editing the config file manually while the app is running.
- If the mismatch is expected, pass the current on-disk content as `expected` deliberately.
Example fix
// before (stale snapshot) let doc = ConfigDocument::load(&path)?; // ... long work ... doc.replace_config_document_if_unchanged(&expected, &new_body)?; // fails if file moved // after (reload just before save) let doc = ConfigDocument::load(&path)?; // fresh load immediately before replace let expected = doc.raw_bytes().to_vec(); doc.replace_config_document_if_unchanged(&expected, &new_body)?;
Defensive patterns
Strategy: retry
Validate before calling
// Compare current on-disk bytes to your snapshot before saving
let current = std::fs::read(&path).ok();
if current.as_deref() != Some(expected.as_slice()) {
eprintln!("config on disk changed since load; reload and merge before saving");
} Try / catch
match doc.replace_config_document_if_unchanged(&expected, &new_body) {
Err(e) if e.to_string().contains("config changed after it was loaded") => {
// reload, re-apply your edit on the fresh document, retry once
let doc = ConfigDocument::load(&path)?;
let fresh = doc.raw_bytes().to_vec();
let merged = merge_my_changes(&fresh, &my_edit);
doc.replace_config_document_if_unchanged(&fresh, &merged)?
}
other => other?,
} Prevention
- Load the config document immediately before saving; never hold snapshots across long operations.
- Avoid hand-editing config.toml while the app is running.
- Design concurrent writers to route changes through the same API instead of raw file writes.
- On conflict, reload and merge rather than force-writing your snapshot.
When it happens
Trigger: Calling save/create_config_document_if_unchanged with an `expected` snapshot that no longer matches the file on disk — e.g. two CLI instances or an external editor modified the config between load and write.
Common situations: Running two codewhale instances concurrently; editing config.toml in an editor while the app writes settings; provisioning scripts writing the file during a tool-driven change.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- {}
- thread permissions changed during update; refresh the…
- a CNB access token is not configured in the Codewhale…
- A saved trace identifier collided. Try saving again.
- a window change is already in progress
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/ddf1b42118beb8a1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/config_document.rs:111
/// 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 73e0f67d83)