warpdotdev/warp · error

Truncated tasks for forked conversation at block are empty f

Error message

Truncated tasks for forked conversation at block are empty for conversation {}.

What it means

In the fork-at-block flow of BlocklistAIHistoryModel, each existing task is truncated at the fork point (`truncated_task` computed per task, `None` when the task has nothing past/before the fork). If every task truncates to None — or the conversation has no tasks at all — `truncated_tasks` is empty and the model refuses to build the fork because there would be no content to persist or show.

Source

Thrown at app/src/ai/blocklist/history_model.rs:1854

                    if truncated_task.messages.is_empty() {
                        return None;
                    }
                    if let Some(fork_point_exchange) = fork_point_exchange_by_task.get(t.id()) {
                        reconcile_dangling_tool_calls_in_forked_task(
                            &mut truncated_task,
                            &source_task.messages,
                            &fork_point_exchange.added_message_ids,
                        );
                    }
                    Some(truncated_task)
                } else {
                    None
                }
            })
            .collect();

        if truncated_tasks.is_empty() {
            return Err(anyhow!(
                "Truncated tasks for forked conversation at block are empty for conversation {}.",
                conversation.id()
            ));
        }

        let updated_tasks_with_new_ids =
            update_forked_task_properties(truncated_tasks, prefix, false, title_override);

        let Some(sqlite_sender) = GlobalResourceHandlesProvider::as_ref(app)
            .get()
            .model_event_sender
            .clone()
        else {
            return Err(anyhow!("No sqlite sender available."));
        };

        // We preserve reverted action IDs. Orphaned IDs (for actions not in fork) are harmless.
        // The reverted states are only copied to the new conversation if the revert happened before the user clicked fork,

View on GitHub (pinned to e72fd7aacb)

Solutions

  1. Verify the conversation actually has tasks loaded (hydrated from cloud/DB) before offering fork-at-block in the UI
  2. Before calling, check that at least one task's messages intersect the fork exchange's added_message_ids
  3. If tasks should exist, wait for hydration to complete and retry the fork
  4. Surface a user-facing 'nothing to fork at this block' message instead of an internal anyhow error

Example fix

// before
let forked = model.fork_conversation_at_block(conv_id, exchange_id)?;

// after: guard the precondition at the call site
let has_content = model
    .conversation(&conv_id)
    .map(|c| !c.tasks().is_empty())
    .unwrap_or(false);
anyhow::ensure!(has_content, "Conversation has no tasks to fork at this block");
let forked = model.fork_conversation_at_block(conv_id, exchange_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// Before forking at a block, confirm the conversation has tasks that cross the fork point.
let conv = BlocklistAIHistoryModel::as_ref(ctx).conversation(&conversation_id);
anyhow::ensure!(
    conv.as_ref().map(|c| !c.tasks().is_empty()).unwrap_or(false),
    "Conversation has no tasks to fork at this block"
);

Type guard

fn can_fork_at_block(conv: &AIConversation, fork_point_exchange: &AIExchange) -> bool {
    !conv.tasks().is_empty()
        && conv
            .tasks()
            .iter()
            .any(|t| messages_intersect(t, &fork_point_exchange.added_message_ids))
}

Try / catch

match model.fork_conversation_at_block(conv_id, exchange_id) {
    Ok(forked) => forked,
    Err(e) if e.to_string().contains("Truncated tasks for forked conversation") => {
        // benign precondition failure: tell the user there is nothing to fork here
        show_info_toast("Nothing to fork at this block");
        return Ok(None);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling fork-at-block on a conversation where no task's messages intersect the fork_point_exchange.added_message_ids: the chosen block precedes all task content, the exchange's message ids belong to no task, or the conversation's tasks have not been loaded/hydrated yet (cloud or DB fetch still in flight).

Common situations: Fork clicked on the very first block before any agent turn; forking a cloud conversation whose tasks are not yet hydrated locally; fork-point exchange id pointing at a system/placeholder exchange with no task messages.

Related errors


AI-assisted analysis of warpdotdev/warp@e72fd7aacb (2026-08-16). Data as JSON: /api/errors/2b3c6e829673f035. Report an issue: GitHub.