Hmbown/CodeWhale · warning · anyhow::Error
At least one thread field is required
Error message
At least one thread field is required
What it means
update_thread was called with an UpdateThreadRequest in which every optional field (archived, allow_shell, trust_mode, auto_approve, model, mode, permission_posture, title, system_prompt, workspace) is None. The API rejects no-op patches instead of performing a load-and-save cycle that would emit an empty change record.
Source
Thrown at crates/tui/src/runtime_threads.rs:4639
self.flush_recovery_receipts_for_thread(id).await?;
self.store
.load_thread(id)
.with_context(|| format!("Thread not found: {id}"))
}
pub async fn update_thread(&self, id: &str, req: UpdateThreadRequest) -> Result<ThreadRecord> {
if req.archived.is_none()
&& req.allow_shell.is_none()
&& req.trust_mode.is_none()
&& req.auto_approve.is_none()
&& req.model.is_none()
&& req.mode.is_none()
&& req.permission_posture.is_none()
&& req.title.is_none()
&& req.system_prompt.is_none()
&& req.workspace.is_none()
{
bail!("At least one thread field is required");
}
if let Some(model) = req.model.as_ref()
&& model.trim().is_empty()
{
bail!("model must not be empty");
}
if let Some(mode) = req.mode.as_ref()
&& mode.trim().is_empty()
{
bail!("mode must not be empty");
}
if let Some(permission_posture) = req.permission_posture.as_ref()
&& permission_posture.trim().is_empty()
{
bail!("permission_posture must not be empty");
}
if let Some(workspace) = req.workspace.as_ref()View on GitHub (pinned to 0c42157ee5)
Solutions
- Only send fields that actually changed - diff against the thread record client-side first
- Skip the update call entirely when the change set is empty
- If using PATCH-style JSON, ensure at least one key is present before sending
- Check the struct after deserialization and reject empty bodies at your own API boundary with a clearer message
Example fix
// before
let req = UpdateThreadRequest::default();
manager.update_thread(id, req).await?; // bails: no fields
// after
let mut req = UpdateThreadRequest::default();
req.title = Some("New title".to_string());
manager.update_thread(id, req).await?; Defensive patterns
Strategy: validation
Validate before calling
// Diff before patching: skip empty requests.
fn has_any_field(req: &UpdateThreadRequest) -> bool {
req.archived.is_some() || req.allow_shell.is_some() || req.trust_mode.is_some()
|| req.auto_approve.is_some() || req.model.is_some() || req.mode.is_some()
|| req.permission_posture.is_some() || req.title.is_some()
|| req.system_prompt.is_some() || req.workspace.is_some()
}
if !has_any_field(&req) {
return Ok(current_record); // no-op: nothing to update
}
manager.update_thread(id, req).await?; Type guard
fn is_empty_update(req: &UpdateThreadRequest) -> bool {
req.archived.is_none() && req.allow_shell.is_none() && req.trust_mode.is_none()
&& req.auto_approve.is_none() && req.model.is_none() && req.mode.is_none()
&& req.permission_posture.is_none() && req.title.is_none()
&& req.system_prompt.is_none() && req.workspace.is_none()
} Prevention
- Build the request from a client-side diff of changed fields, not a defaults-initialized struct
- Reject empty PATCH bodies at your own API boundary with a clearer message
- Gate 'save' actions in UIs on at least one changed field
When it happens
Trigger: Constructing UpdateThreadRequest::default() (or an all-None deserialization from an empty JSON PATCH body) and calling update_thread. Check at runtime_threads.rs:4630-4640.
Common situations: A client builds the request from a diff object that came up empty; serde deserializes {} into all-None; a UI 'save' button fires with no changed fields.
Related errors
- model must not be empty
- Antigravity cloud-code does not accept role {other:?}
- Antigravity cloud-code request has no text contents
- Patch is empty.
- git apply failed: {}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/871baa7f90f12691.
Report an issue: GitHub.