{"record":{"id":"e58786a8e0f73b63","repo":"Hmbown/CodeWhale","slug":"continual-harness-entry-id-cannot-be-empty","errorCode":null,"errorMessage":"continual harness entry id cannot be empty","messagePattern":"continual harness entry id cannot be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/continual_harness.rs","lineNumber":138,"sourceCode":"            content: refinement.content,\n            evidence: refinement.evidence,\n        };\n        state.schema_version = SCHEMA_VERSION;\n        state.entries.push(entry.clone());\n        save_state(&path, &state)?;\n        // Journalled after the state is durable: a logged edit that never\n        // landed would be worse than an unlogged one.\n        append_journal(&path, \"refine\", &entry)?;\n        Ok(entry)\n    })\n}\n\n/// Remove one exact entry. Returning the removed entry makes deletion\n/// receipts useful without re-reading the state file.\npub fn remove(workspace: &Path, id: &str) -> Result<HarnessEntry> {\n    let id = id.trim();\n    if id.is_empty() {\n        bail!(\"continual harness entry id cannot be empty\");\n    }\n    let path = state_path_for_write(workspace)?;\n    with_write_lock(&path, || {\n        let mut state = load_state(&path)?;\n        let index = state\n            .entries\n            .iter()\n            .position(|entry| entry.id == id)\n            .ok_or_else(|| anyhow!(\"continual harness has no entry `{id}`\"))?;\n        let removed = state.entries.remove(index);\n        state.schema_version = SCHEMA_VERSION;\n        save_state(&path, &state)?;\n        // Removal is the edit most worth recording: the entry is gone from\n        // state, so the journal is the only place its content survives.\n        append_journal(&path, \"remove\", &removed)?;\n        Ok(removed)\n    })\n}","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/continual_harness.rs#L120-L156","documentation":"Thrown by continual_harness::remove when the entry id, after trimming, is empty. Ids are opaque strings like \"h_<uuid-simple>\"; the guard rejects blank input before taking the write lock so a caller cannot issue a meaningless delete.","triggerScenarios":"Calling remove(workspace, \"\") or remove(workspace, \"   \") — e.g. a UI passing an unselected row's id, or a script forwarding an unset variable.","commonSituations":"A tool caller forwarding an empty selection; string building that drops the id; copy-paste from a truncated example.","solutions":["Pass the exact id from overview(workspace).entries (format h_<32-hex>)","Check the id is non-empty after trimming before calling remove","If the id came from user selection, validate a row is actually selected"],"exampleFix":"// before\ncontinual_harness::remove(&workspace, \"\")?;\n\n// after\nlet id = selected_entry.id.clone();\ncontinual_harness::remove(&workspace, &id)?;","handlingStrategy":"validation","validationCode":"let id = id.trim();\nif id.is_empty() {\n    return Err(anyhow::anyhow!(\"select an entry to remove first\"));\n}\nlet removed = continual_harness::remove(&workspace, id)?;","typeGuard":"fn is_valid_entry_id(id: &str) -> bool {\n    let id = id.trim();\n    !id.is_empty() && id.starts_with(\"h_\") && id.len() == 2 + 32\n}","tryCatchPattern":null,"preventionTips":["Always take ids from overview(workspace).entries instead of constructing them","Require an explicit selection before enabling the remove action in the UI","Treat a blank id as a caller bug, not a harness state problem"],"tags":["rust","tui","harness","input-validation"],"backgroundTag":"missing-required-parameter","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}