Hmbown/CodeWhale · error
commit
Error message
commit
What it means
This panic comes from `transaction.commit().expect("commit")` at crates/config/src/tests.rs:4303. `SetupTransaction::commit` applies every staged file atomically: it writes/renames staged bodies into place and, if any stage fails to apply, rolls back already-applied stages byte-for-byte. The panic fires when any staged write fails (unwritable target, parent path occupied by a file, missing directory) — either as an outright commit failure or as a rollback that still surfaces an error.
Solutions
- Ensure every staged path's parent is an existing writable directory
- Remove any file occupying a staged path
- Re-run the setup transaction after clearing external locks/concurrent writers
Example fix
// before
transaction.commit().expect("commit");
// after
if let Err(e) = transaction.commit() {
eprintln!("commit failed and rolled back: {e:#}");
// config.toml restored byte-for-byte; fix the blocker and retry
fs::remove_file(&blocker).expect("remove blocker");
transaction.commit().expect("commit after unblock");
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify every staged target's parent is a writable directory before commit
for path in staged_paths {
ensure!(path.parent().map_or(false, |p| p.is_dir()), "parent not a dir: {}", path.display());
} Try / catch
match transaction.commit() {
Err(e) => {
// rollback already restored config.toml byte-for-byte; fix cause and retry
eprintln!("setup rolled back: {e:#}");
retry_after_fix()
}
Ok(()) => {}
} Prevention
- Never let a regular file occupy a directory path used by setup
- Close external file handles/locks before committing on Windows
- Leave enough disk space for atomic renames
- Rely on the rollback guarantee: after a failed commit, verify the original file is intact
When it happens
Trigger: Committing a transaction where a staged target path is unwritable or its parent is a regular file (e.g. the 'blocker' pattern), a staged path was removed between stage and commit, or rename/replace fails at the OS level (cross-device, permissions, EBUSY).
Common situations: Another process holding the config file open on Windows; a blocker file occupying a directory path; disk full during the atomic replace; setup racing a concurrent writer.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- overwrite
- skill version marker should have a parent directory
- store
- approval log has no parent
- create isolated home
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/52180a3c213892d0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/tests.rs:4303
let state_path = dir.path().join(crate::setup_state::SETUP_STATE_FILE_NAME);
fs::write(
&config_path,
"# my model\nmodel = \"deepseek-v4-flash\"\n# end comment\n",
)
.expect("write config");
let mut store = ConfigStore::load(Some(config_path.clone())).expect("load config store");
store.config.model = Some("deepseek-v4-pro".to_string());
let mut transaction = persistence::SetupTransaction::new();
transaction.stage(
&config_path,
store.rendered_body().expect("rendered body").into_bytes(),
);
transaction
.stage_json(&state_path, &SetupState::default())
.expect("stage setup state");
transaction.commit().expect("commit");
let body = fs::read_to_string(&config_path).expect("read config");
assert!(body.contains("# my model"), "prefix comment preserved");
assert!(body.contains("# end comment"), "suffix comment preserved");
assert!(body.contains("model = \"deepseek-v4-pro\""));
assert!(state_path.exists(), "sibling setup state written");
}
#[test]
fn setup_transaction_rolls_back_config_store_body_on_sibling_failure() {
// #3410 rollback expectation: when a sibling stage fails to apply, the
// already-written config.toml is restored byte-for-byte, comments and
// all — no half-applied setup.
let dir = tempfile::tempdir().expect("tempdir");
let config_path = dir.path().join(CONFIG_FILE_NAME);
let original = "# my model\nmodel = \"deepseek-v4-flash\"\n# end comment\n";
fs::write(&config_path, original).expect("write config");
// A parent that is a regular file makes the second stage unwritable.View on GitHub (pinned to 433685b202)