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

  1. Re-read the thread's entries and use a currently-present user message id before restoring
  2. On receiving this error, refresh client-side thread state instead of retrying the same id
  3. 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

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


AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16). Data as JSON: /api/errors/7eae4d6354bbfe31. Report an issue: GitHub.