Hmbown/CodeWhale · error
persisted image input is missing its prompt
Error message
persisted image input is missing its prompt
What it means
When loading a persisted image-attachment turn input, the runtime requires a non-empty user prompt (text) to accompany the image content blocks. The store decoded the content successfully, but every text block was empty or whitespace-only, so the record is rejected. This guards against sessions that would otherwise send an image to the model with no instruction.
Solutions
- Re-open the message in the TUI and type a prompt before sending, then re-save/persist the record.
- Edit the persisted session record to add a non-empty text ContentBlock (or a non-empty `detail`) alongside the image blocks.
- Restore the session file from backup or start a new thread, re-attaching the image with a prompt.
- If caused by a version/migration path, update to a build whose attachment flow persists the prompt with the image.
Example fix
// before: persisted content has only an image block
[{"type":"image", "source":{...}}]
// after: include the prompt text block
[{"type":"text", "text":"Describe this screenshot"},{"type":"image", "source":{...}}] Defensive patterns
Strategy: validation
Validate before calling
let has_prompt = blocks.iter().any(|b| matches!(b, ContentBlock::Text { text, .. } if !text.trim().is_empty()));
if !has_prompt {
anyhow::bail!("attach a prompt before persisting image input");
} Type guard
fn has_nonempty_text(blocks: &[ContentBlock]) -> bool {
blocks.iter().any(|b| matches!(b, ContentBlock::Text { text, .. } if !text.trim().is_empty()))
} Try / catch
match load_persisted_image_input(record) {
Ok(blocks) => send(blocks),
Err(e) if e.to_string().contains("missing its prompt") => prompt_user_for_text_then_resend(),
Err(e) => return Err(e),
} Prevention
- Always capture the user prompt in the same save as the image attachment (single atomic write).
- Reject image-only submissions in the UI before persisting.
- Add a round-trip test that loads every persisted record and re-validates it.
When it happens
Trigger: Calling the persisted-record loader (detail-to-content-blocks conversion in runtime_threads.rs) for a turn whose ContentBlock JSON parses but contains only image blocks, or whose `detail` field is empty/whitespace while no non-empty text block exists in the content.
Common situations: A session file was hand-edited or migrated and its text block was stripped; an older build crashed mid-save leaving image blocks without the prompt; a bug in the attachment flow wrote image content before capturing the detail text.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- invalid persisted user image content kind
- Invalid pet world checkpoint.
- Invalid version 1 pet bucket.
- persisted image input must contain an image
- provider catalog scope
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d6334217fcbb8953.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:1441
|| (self.schema_version == OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION
&& self
.metadata
.as_ref()
.is_some_and(|meta| meta.get("runtime_image_content").is_some())))
&& self.kind == TurnItemKind::UserMessage
{
let content = self
.metadata
.as_ref()
.and_then(|meta| meta.get("runtime_image_content"))
.context("persisted image input is missing its content")?;
let blocks: Vec<ContentBlock> =
serde_json::from_value(content.clone()).context("invalid persisted image input")?;
crate::image_attach::validate_stored_image_content(&blocks)?;
if self.detail.as_deref().is_some_and(|text| !text.trim().is_empty())
&& !blocks.iter().any(|block| matches!(block, ContentBlock::Text { text, .. } if !text.trim().is_empty()))
{
bail!("persisted image input is missing its prompt");
}
return Ok(blocks);
}
let text = self.detail.as_ref().unwrap_or(&self.summary);
Ok(if text.trim().is_empty() {
Vec::new()
} else {
vec![ContentBlock::Text {
text: text.clone(),
cache_control: None,
}]
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeEventRecord {
#[serde(default = "default_runtime_schema_version")]View on GitHub (pinned to 73e0f67d83)