Hmbown/CodeWhale · warning
read backup
Error message
read backup
What it means
fs::read_to_string(backup_path).expect("read backup") in crates/config/src/tests.rs:4207 panics when the scrubbed backup file cannot be read back as a UTF-8 string. The receipt type ImportReceipt (crates/cli/src/config_bundles.rs:1001) exposes backup_path: Option<PathBuf>, and a None backup (apply_prepared_bundle returns backup_path: None when no backup was made) is a frequent root cause when tests are adapted to use receipt-backed paths.
Solutions
- Handle the Option: match receipt.backup_path and skip/adjust the assertion when no backup was created.
- Verify the scrub step does not delete or rename the backup before the read.
- Check the io::Error kind in the panic: NotFound means the file vanished; InvalidData means non-UTF-8 content.
Example fix
// before
let backup = fs::read_to_string(backup_path).expect("read backup");
// after
let backup = fs::read_to_string(
receipt.backup_path.as_ref().expect("backup should exist"),
).expect("read backup"); Defensive patterns
Strategy: type-guard
Validate before calling
let Some(backup_path) = receipt.backup_path.as_deref() else {
eprintln!("no backup was created for this import");
return;
}; Type guard
fn backup_of(receipt: &ImportReceipt) -> Option<&std::path::Path> {
receipt.backup_path.as_deref()
} Try / catch
let backup = fs::read_to_string(backup_path)
.unwrap_or_else(|e| panic!("read backup at {backup_path:?} failed: {e}")); Prevention
- Treat ImportReceipt.backup_path as Option<PathBuf> — None means no backup was made
- Never delete or rename the backup between scrub and read
- Check the io::Error kind: NotFound vs InvalidData (non-UTF-8)
When it happens
Trigger: Reading backup_path when it is None (unwrap on Option before read), the file was deleted/moved by the scrub step, or the scrub wrote invalid UTF-8.
Common situations: Refactoring tests to derive paths from ImportReceipt.backup_path and forgetting it is Option<PathBuf>; scrub implementation change removing the backup file.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/1c5e8bf2f53539e6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/tests.rs:4207
assert!(backup.contains("auth_mode = \"api_key\""));
assert!(backup.contains("default_text_model = \"deepseek-v4-pro\""));
}
#[test]
fn config_backup_scrub_repairs_an_existing_plaintext_backup() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join(CONFIG_FILE_NAME);
fs::write(&path, "model = \"new-model\"\n").expect("seed config");
let backup_path = config_backup_path(&path);
fs::write(
&backup_path,
"api_key = \"old-test-credential\"\nmodel = \"old-model\"\n",
)
.expect("seed backup");
scrub_plaintext_api_keys_from_config_backup(&path).expect("scrub backup");
let backup = fs::read_to_string(backup_path).expect("read backup");
assert!(!backup.contains("old-test-credential"), "{backup}");
assert!(!backup.contains("api_key"), "{backup}");
assert!(backup.contains("model = \"old-model\""));
}
#[test]
fn config_store_save_preserves_comments() {
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");
let mut store = ConfigStore::load(Some(config_path.clone())).expect("load config store");
store.config.model = Some("deepseek-v4-pro".to_string());
store.save().expect("save");
let body = fs::read_to_string(&config_path).expect("read config");
assert!(body.contains("# my model"), "prefix comment preserved");View on GitHub (pinned to 433685b202)