Hmbown/CodeWhale · error · anyhow::Error
Agent Mail ownership denied: sender identity does not own th
Error message
Agent Mail ownership denied: sender identity does not own the source task/session
What it means
queue_agent_mail derived the expected sender identity from the source thread (agent_mail_sender_identity) and it does not match request.sender.identity. Only the identity that owns the source task/session may send mail in its name; a mismatch means the request is spoofing (or misconfiguring) the sender.
Source
Thrown at crates/tui/src/runtime_threads.rs:3626
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"
);
}
let (envelope, idempotent_replay) = {
let _mail_mutation = self.store.mail_mutation.lock();
let path = self.store.mail_path(&request.message_id)?;
if path.exists() {
let persisted = self.store.load_agent_mail(&request.message_id)?;
if !persisted.matches_send_request(&request) {
bail!(
"Agent Mail message id '{}' already exists with different delivery intent",
request.message_id
);
}
(persisted, true)
} else {
let envelope = AgentMailEnvelope {View on GitHub (pinned to 0c42157ee5)
Solutions
- Derive sender.identity from the source thread's own record (the same way agent_mail_sender_identity does) instead of accepting it from the client
- When forwarding, set sender to the forwarding thread's identity and increment hop_count
- Update stale templates/fixtures after any identity-format change
- Treat this error as a possible spoofing attempt in server-side code: log and reject, do not auto-correct
Defensive patterns
Strategy: validation
Validate before calling
// Derive the sender identity from the source thread instead of trusting client input. request.sender.identity = sender_identity_of(&manager.get_thread(&request.source_thread_id).await?); manager.queue_agent_mail(request).await?;
Prevention
- Never hand-craft sender.identity; derive it from the source thread record
- When forwarding mail, re-sign as the forwarding thread and bump hop_count
- After identity-format changes, refresh stored templates and fixtures
When it happens
Trigger: Passing a hand-crafted sender.identity that differs from the identity the source thread was created with - e.g. reusing a sender block from another thread, or an agent forwarding mail without rewriting sender to its own identity. Check at runtime_threads.rs:3625-3629.
Common situations: Copy-pasted request templates with a stale identity; hop-forwarding code that preserves the original sender instead of re-signing as the forwarding thread; identity format drift after a protocol version change.
Related errors
- Agent Mail ownership denied: source and destination must bel
- Agent Mail ownership denied: message does not belong to this
- external credential access is disabled for {}
- tool '{tool_name}' on MCP server '{server_name}' is blocked
- MCP tool '{tool_name}' is disabled for server '{server_name}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/08ffc1c29d01e945.
Report an issue: GitHub.