Hmbown/CodeWhale · error · anyhow::Error

Agent Mail ownership denied: message does not belong to this

Error message

Agent Mail ownership denied: message does not belong to this destination

What it means

mark_agent_mail_read resolved the calling thread's address and the loaded envelope's destination does not equal it. Only the destination thread may transition an envelope to Read; the source (or any third thread) cannot mark mail read on someone else's behalf.

Source

Thrown at crates/tui/src/runtime_threads.rs:3706

                .collect::<Vec<_>>();
            Ok(inbox)
        })
        .await
        .context("Agent Mail inbox task panicked")?
    }

    pub async fn mark_agent_mail_read(
        &self,
        thread_id: &str,
        message_id: &AgentMailMessageId,
    ) -> Result<AgentMailEnvelope> {
        let thread = self.get_thread(thread_id).await?;
        let address = agent_mail_address(&self.store.owner_id, &thread)?;
        let envelope = {
            let _mail_mutation = self.store.mail_mutation.lock();
            let mut envelope = self.store.load_agent_mail(message_id)?;
            if envelope.destination != address {
                bail!("Agent Mail ownership denied: message does not belong to this destination");
            }
            match envelope.status {
                AgentMailStatus::Read => envelope,
                AgentMailStatus::Delivered => {
                    envelope.status = AgentMailStatus::Read;
                    envelope.read_at = Some(Utc::now());
                    self.store.save_agent_mail(&envelope)?;
                    envelope
                }
                _ => bail!("Agent Mail can be marked read only after delivery"),
            }
        };
        self.emit_agent_mail_event(AGENT_MAIL_EVENT_READ, &envelope)
            .await?;
        Ok(envelope)
    }

    /// Claim and project one envelope into the existing destination turn

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Fetch the destination thread's mail list first and only mark-read ids that appear in it
  2. Ensure the client passes the recipient thread's id, not the sender's or a global one
  3. Validate envelope.destination against the calling thread before invoking if the id came from untrusted input
  4. Check for copy-paste of message_id from a different envelope
Defensive patterns

Strategy: validation

Validate before calling

// Only mark read mail that is addressed to the calling thread.
let envelope = manager.load_agent_mail(message_id).await?;
if !envelope.destination_belongs_to(thread_id) { // compare address parts as the runtime does
    return Err(anyhow::anyhow!("message not addressed to this thread"));
}
manager.mark_agent_mail_read(thread_id, message_id).await?;

Prevention

When it happens

Trigger: Calling mark_agent_mail_read(thread_id, message_id) where message_id was sent to a different thread - wrong message id from a list, or a client passing the source/author thread instead of the recipient. Check at runtime_threads.rs:3704-3707.

Common situations: UI lists all mail for an owner and passes the wrong thread context; a script iterates mailboxes and reuses one thread_id for all messages; message id typo or truncation.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/758928e5ecd9e02d. Report an issue: GitHub.