Hmbown/CodeWhale · error · anyhow::Error
Agent Mail source and destination threads must differ
Error message
Agent Mail source and destination threads must differ
What it means
queue_agent_mail requires source_thread_id != destination_thread_id. Mail is a cross-thread/cross-task handoff primitive; self-addressed mail would let a thread trigger its own turn (a mail loop) and is rejected before any persistence happens.
Source
Thrown at crates/tui/src/runtime_threads.rs:3610
if agent_mail_looks_like_raw_transcript(&request.summary) {
bail!("Agent Mail accepts a bounded handoff summary, not a raw transcript");
}
request.summary = sanitize_agent_mail_text(&request.summary, MAX_AGENT_MAIL_SUMMARY_BYTES);
request.sender.display_label = sanitize_agent_mail_text(
&request.sender.display_label,
codewhale_protocol::agent_mail::MAX_AGENT_MAIL_DISPLAY_LABEL_BYTES,
);
for evidence in &mut request.evidence {
if let Some(label) = evidence.label.as_mut() {
*label = sanitize_agent_mail_text(
label,
codewhale_protocol::agent_mail::MAX_AGENT_MAIL_EVIDENCE_LABEL_BYTES,
);
}
}
request.validate().map_err(|error| anyhow!(error))?;
if request.source_thread_id == request.destination_thread_id {
bail!("Agent Mail source and destination threads must differ");
}
let source_thread = self.get_thread(&request.source_thread_id).await?;
let destination_thread = self.get_thread(&request.destination_thread_id).await?;
let source = agent_mail_address(&self.store.owner_id, &source_thread)?;
let destination = agent_mail_address(&self.store.owner_id, &destination_thread)?;
if source.owner_id != destination.owner_id
|| source.workspace_id != destination.workspace_id
{
bail!(
"Agent Mail ownership denied: source and destination must belong to the same runtime owner and workspace"
);
}
let expected_sender = agent_mail_sender_identity(&source_thread)?;
if request.sender.identity != expected_sender {
bail!(
"Agent Mail ownership denied: sender identity does not own the source task/session"
);View on GitHub (pinned to 0c42157ee5)
Solutions
- Fix the caller: destination must be a different thread - check the two ids differ before sending
- If a self-reminder was intended, use the thread's normal input path instead of Agent Mail
- Log both ids at the call site to catch the copy-paste/variable-reuse origin
- Add a debug_assert!(source != destination) in your wrapper before queue_agent_mail
Example fix
// before
let request = AgentMailSendRequest {
source_thread_id: thread_id.clone(),
destination_thread_id: thread_id.clone(), // bug: same thread
..
};
// after
let request = AgentMailSendRequest {
source_thread_id: thread_id.clone(),
destination_thread_id: peer_thread_id.clone(),
..
}; Defensive patterns
Strategy: validation
Validate before calling
if request.source_thread_id == request.destination_thread_id {
return Err(anyhow::anyhow!("agent mail requires distinct source and destination threads"));
}
manager.queue_agent_mail(request).await?; Prevention
- Set destination from an explicitly resolved peer thread, never by copying the source variable
- Debug-assert source != destination in wrappers and test fixtures
- Remember mail is a cross-thread handoff; self-mail has no valid meaning
When it happens
Trigger: Constructing AgentMailSendRequest with the same thread id on both sides - typically a variable reuse bug (source copied into destination) or an agent mailing its own thread because it lost track of its identity. Checked at runtime_threads.rs:3610-3612 after validate().
Common situations: An orchestrator iterates threads and accidentally sets destination from the loop variable it used for source; an agent that does not know its own thread_id defaults both to the current thread.
Related errors
- Antigravity cloud-code does not accept role {other:?}
- Agent Mail accepts a bounded handoff summary, not a raw tran
- Agent Mail ownership denied: source and destination must bel
- Agent Mail ownership denied: sender identity does not own th
- Agent Mail message id '{}' already exists with different del
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/f317d9231fb6a95f.
Report an issue: GitHub.