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

  1. Handle the Option: match receipt.backup_path and skip/adjust the assertion when no backup was created.
  2. Verify the scrub step does not delete or rename the backup before the read.
  3. 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

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)