zed-industries/zed · error
message not found
Error message
message not found
What it means
restore_checkpoint() requires a ClientUserMessageId that resolves to a user message entry via user_message_mut(); when no entry with that id exists it returns a ready Err task 'message not found'. The id must belong to a live user message in this thread (that is where checkpoints are stored).
Source
Thrown at crates/acp_thread/src/acp_thread.rs:3989
continue;
};
if self
.elicitations
.cancel_elicitation_by_id(elicitation_id, true)
{
cx.emit(AcpThreadEvent::EntryUpdated(ix));
}
}
}
/// Restores the git working tree to the state at the given checkpoint (if one exists)
pub fn restore_checkpoint(
&mut self,
client_id: ClientUserMessageId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let Some((_, message)) = self.user_message_mut(&client_id) else {
return Task::ready(Err(anyhow!("message not found")));
};
let checkpoint = message
.checkpoint
.as_ref()
.map(|c| c.git_checkpoint.clone());
// Cancel any in-progress generation before restoring
let cancel_task = self.cancel(cx);
let rewind = self.rewind(client_id.clone(), cx);
let git_store = self.project.read(cx).git_store().clone();
cx.spawn(async move |_, cx| {
cancel_task.await;
rewind.await?;
if let Some(checkpoint) = checkpoint {
git_store
.update(cx, |git, cx| git.restore_checkpoint(checkpoint, cx))View on GitHub (pinned to bc538def45)
Solutions
- Re-read the thread's entries and use a currently-present user message id before restoring
- On receiving this error, refresh client-side thread state instead of retrying the same id
- Debounce/disable the restore control while a restore is in flight
Example fix
// before
let task = thread.update(cx, |thread, cx| {
thread.restore_checkpoint(client_id.clone(), cx)
});
// after: verify the id is still a live user message first
let exists = thread.read(cx).entries.iter().any(|entry|
matches!(entry, ThreadEntry::User(m) if m.client_id == client_id));
if !exists {
return Task::ready(Err(anyhow!("message not found"))); // refresh UI state instead
}
let task = thread.update(cx, |thread, cx| {
thread.restore_checkpoint(client_id.clone(), cx)
}); Defensive patterns
Strategy: validation
Validate before calling
// Verify the message id is a live user entry before restoring
let is_live = thread.read(cx).entries.iter().any(|entry| {
matches!(entry, ThreadEntry::User(m) if m.client_id == client_id)
});
if !is_live {
refresh_thread_state(); // re-fetch entries; do not call restore_checkpoint
return;
} Try / catch
let task = thread.update(cx, |thread, cx| thread.restore_checkpoint(client_id.clone(), cx));
if let Err(error) = task.await {
if error.to_string().contains("message not found") {
// Stale id after a rewind — refresh entries and let the user re-pick.
refresh_thread_state();
} else {
return Err(error);
}
} Prevention
- Derive checkpoint ids from freshly-read thread state, never from cached UI state
- Debounce restore buttons so double activations cannot race a rewind
- After any rewind/restore completes, invalidate cached message ids client-side
When it happens
Trigger: Client sends restore_checkpoint with a message id that was rewound away (the entry was truncated out of the thread), an id from a different thread, or an id cached before the thread was cleared/restored from the database.
Common situations: Client UI keeps a stale checkpoint id across a rewind; double-click/racing restore where the first call rewinds entries and the second one misses; client reconnected after agent restart while reusing pre-restart message ids.
Related errors
- no thread found with ID: {id:?}
- Project state not found for session
- Session not found
- output token limit reached
- not supported
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/7eae4d6354bbfe31.
Report an issue: GitHub.