{"record":{"id":"52180a3c213892d0","repo":"Hmbown/CodeWhale","slug":"commit","errorCode":null,"errorMessage":"commit","messagePattern":"commit","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/tests.rs","lineNumber":4303,"sourceCode":"    let state_path = dir.path().join(crate::setup_state::SETUP_STATE_FILE_NAME);\n    fs::write(\n        &config_path,\n        \"# my model\\nmodel = \\\"deepseek-v4-flash\\\"\\n# end comment\\n\",\n    )\n    .expect(\"write config\");\n\n    let mut store = ConfigStore::load(Some(config_path.clone())).expect(\"load config store\");\n    store.config.model = Some(\"deepseek-v4-pro\".to_string());\n\n    let mut transaction = persistence::SetupTransaction::new();\n    transaction.stage(\n        &config_path,\n        store.rendered_body().expect(\"rendered body\").into_bytes(),\n    );\n    transaction\n        .stage_json(&state_path, &SetupState::default())\n        .expect(\"stage setup state\");\n    transaction.commit().expect(\"commit\");\n\n    let body = fs::read_to_string(&config_path).expect(\"read config\");\n    assert!(body.contains(\"# my model\"), \"prefix comment preserved\");\n    assert!(body.contains(\"# end comment\"), \"suffix comment preserved\");\n    assert!(body.contains(\"model = \\\"deepseek-v4-pro\\\"\"));\n    assert!(state_path.exists(), \"sibling setup state written\");\n}\n\n#[test]\nfn setup_transaction_rolls_back_config_store_body_on_sibling_failure() {\n    // #3410 rollback expectation: when a sibling stage fails to apply, the\n    // already-written config.toml is restored byte-for-byte, comments and\n    // all — no half-applied setup.\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let config_path = dir.path().join(CONFIG_FILE_NAME);\n    let original = \"# my model\\nmodel = \\\"deepseek-v4-flash\\\"\\n# end comment\\n\";\n    fs::write(&config_path, original).expect(\"write config\");\n    // A parent that is a regular file makes the second stage unwritable.","sourceCodeStart":4285,"sourceCodeEnd":4321,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/config/src/tests.rs#L4285-L4321","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ntransaction.commit().expect(\"commit\");\n// after\nif let Err(e) = transaction.commit() {\n    eprintln!(\"commit failed and rolled back: {e:#}\");\n    // config.toml restored byte-for-byte; fix the blocker and retry\n    fs::remove_file(&blocker).expect(\"remove blocker\");\n    transaction.commit().expect(\"commit after unblock\");\n}","handlingStrategy":"try-catch","validationCode":"// verify every staged target's parent is a writable directory before commit\nfor path in staged_paths {\n    ensure!(path.parent().map_or(false, |p| p.is_dir()), \"parent not a dir: {}\", path.display());\n}","typeGuard":null,"tryCatchPattern":"match transaction.commit() {\n    Err(e) => {\n        // rollback already restored config.toml byte-for-byte; fix cause and retry\n        eprintln!(\"setup rolled back: {e:#}\");\n        retry_after_fix()\n    }\n    Ok(()) => {}\n}","preventionTips":["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"],"tags":["rust","atomic-write","filesystem","setup-transaction"],"backgroundTag":"file-write-failed","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}