Hmbown/CodeWhale · error

could not be read as setup state

Error message

{} could not be read as setup state

What it means

After confirming the sidecar path exists, `load_notice_state_at` calls `SetupState::load_from(path)`; if that returns None (unreadable, corrupt, or wrong-format sidecar) it throws `"{} could not be read as setup state"`. The purpose is protective: an existing but corrupt sidecar must never be replaced by defaults, so the notice gate fails loudly instead.

Solutions

  1. Back up the sidecar file, then delete or rename it so a fresh valid state can be written on next run.
  2. Inspect the file contents and repair it to the expected setup-state format (or restore from a backup taken before an upgrade).
  3. Check logs for the prior write that may have been interrupted; ensure the process is not killed mid-write (clean shutdown).
  4. If corrupt sidecars recur, report it — repeated truncation suggests a locking/fsync bug in the persistence path.

Example fix

// before
mv ~/.config/codewhale/telemetry-notice.json ~/.config/codewhale/telemetry-notice.json.bak
// after
# fresh default state is created on next launch
rm ~/.config/codewhale/telemetry-notice.json.bak
Defensive patterns

Strategy: fallback

Validate before calling

// Rust-style caller pre-check
if path.exists() {
    let raw = std::fs::read(&path)?;
    anyhow::ensure!(!raw.is_empty(), "sidecar {} is empty/corrupt", path.display());
    // optionally attempt a parse here before invoking the library
}

Type guard

fn sidecar_parseable(path: &std::path::Path) -> bool {
    std::fs::read(path).map(|raw| !raw.is_empty()).unwrap_or(false)
}

Try / catch

match load_notice_state_at(&path) {
    Ok(state) => state,
    Err(e) if e.to_string().contains("could not be read as setup state") => {
        // corrupt sidecar: back it up, then fall back to fresh defaults
        let _ = std::fs::rename(&path, path.with_extension("json.corrupt"));
        SetupState::default()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `plan_for_store_and_state` or `apply_persistent_preference_at` when the sidecar exists but its contents fail `SetupState` parsing — truncated write, hand-edited file, empty file, wrong schema version, or the path points at a directory or binary garbage.

Common situations: A crash or power loss during a previous write left a partial sidecar; the user edited the setup-state file manually; a version upgrade changed the on-disk format; the path was repointed at a different non-state 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@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/ac3d92b80c235e0c. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/telemetry_notice.rs:371

    let mut store = codewhale_config::ConfigStore::load(config_path)?;
    store
        .config
        .set_value("telemetry", if enabled { "true" } else { "false" })?;
    store.save()
}

/// Load a missing sidecar as a fresh state, but distinguish it from an
/// existing unreadable/corrupt sidecar so the notice can never overwrite the
/// latter with defaults.
fn load_notice_state_at(path: &Path) -> Result<SetupState> {
    if !path
        .try_exists()
        .map_err(|error| anyhow!("could not inspect {}: {error}", path.display()))?
    {
        return Ok(SetupState::default());
    }
    SetupState::load_from(path)
        .ok_or_else(|| anyhow!("{} could not be read as setup state", path.display()))
}

/// Everything that decides whether the disclosure may be shown.
struct NoticeGate {
    needs_notice: bool,
    persisted_off: bool,
    recorded_opt_out: bool,
    floor_in_force: bool,
}

impl NoticeGate {
    fn may_ask(&self) -> bool {
        self.needs_notice && !self.persisted_off && !self.recorded_opt_out && !self.floor_in_force
    }
}

#[cfg(test)]
mod tests {

View on GitHub (pinned to 73e0f67d83)