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
- Back up the sidecar file, then delete or rename it so a fresh valid state can be written on next run.
- Inspect the file contents and repair it to the expected setup-state format (or restore from a backup taken before an upgrade).
- Check logs for the prior write that may have been interrupted; ensure the process is not killed mid-write (clean shutdown).
- 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
- Do not hand-edit setup-state sidecar files.
- Ensure clean shutdown so writes are never truncated mid-flight.
- After version upgrades, verify sidecar format compatibility or delete stale sidecars.
- Keep a backup of the config directory before upgrades.
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
- already exists; pass --force to overwrite it
- Android loaded image
- approval log has no parent
- artifact name is not portable UTF-8
- artifact tree exceeds export depth limit
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)