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

  1. 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
  2. When forwarding, set sender to the forwarding thread's identity and increment hop_count
  3. Update stale templates/fixtures after any identity-format change
  4. 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

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


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