{"record":{"id":"5d6ac78f5fc4ec1f","repo":"Hmbown/CodeWhale","slug":"agent-mail-ownership-denied-source-and-destinatio","errorCode":null,"errorMessage":"Agent Mail ownership denied: source and destination must belong to the same runtime owner and workspace","messagePattern":"Agent Mail ownership denied: source and destination must belong to the same runtime owner and workspace","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":3620,"sourceCode":"                *label = sanitize_agent_mail_text(\n                    label,\n                    codewhale_protocol::agent_mail::MAX_AGENT_MAIL_EVIDENCE_LABEL_BYTES,\n                );\n            }\n        }\n        request.validate().map_err(|error| anyhow!(error))?;\n        if request.source_thread_id == request.destination_thread_id {\n            bail!(\"Agent Mail source and destination threads must differ\");\n        }\n\n        let source_thread = self.get_thread(&request.source_thread_id).await?;\n        let destination_thread = self.get_thread(&request.destination_thread_id).await?;\n        let source = agent_mail_address(&self.store.owner_id, &source_thread)?;\n        let destination = agent_mail_address(&self.store.owner_id, &destination_thread)?;\n        if source.owner_id != destination.owner_id\n            || source.workspace_id != destination.workspace_id\n        {\n            bail!(\n                \"Agent Mail ownership denied: source and destination must belong to the same runtime owner and workspace\"\n            );\n        }\n        let expected_sender = agent_mail_sender_identity(&source_thread)?;\n        if request.sender.identity != expected_sender {\n            bail!(\n                \"Agent Mail ownership denied: sender identity does not own the source task/session\"\n            );\n        }\n\n        let (envelope, idempotent_replay) = {\n            let _mail_mutation = self.store.mail_mutation.lock();\n            let path = self.store.mail_path(&request.message_id)?;\n            if path.exists() {\n                let persisted = self.store.load_agent_mail(&request.message_id)?;\n                if !persisted.matches_send_request(&request) {\n                    bail!(\n                        \"Agent Mail message id '{}' already exists with different delivery intent\",","sourceCodeStart":3602,"sourceCodeEnd":3638,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L3602-L3638","documentation":"queue_agent_mail resolved both threads to AgentMailAddresses and found the source and destination do not share owner_id and workspace_id. Mail is single-owner, single-workload: envelopes never cross runtime owners or workspaces, so this is an authorization boundary failure, not a connectivity issue.","triggerScenarios":"get_thread resolves either thread whose stored owner/workspace differs from the other - e.g. one id belongs to a thread created under a different owner or workspace layout after a migration, or a guessed/foreign thread id was passed. Check at runtime_threads.rs:3615-3623.","commonSituations":"Mixing thread ids from two runtime deployments or profiles; workspace renamed/re-keyed so one side resolves to the old workspace; test fixtures reusing ids across isolated stores.","solutions":["Verify both thread ids were created by the same runtime owner and workspace (list threads and compare their records)","If the workspace legitimately changed, re-create or migrate the thread record so both sides resolve within one workspace","Never accept destination thread ids from untrusted input - resolve and authorize them server-side","Check for accidental cross-environment id leakage (prod id in a dev runtime)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Both threads must resolve under the same owner and workspace.\nlet src = manager.get_thread(&request.source_thread_id).await?;\nlet dst = manager.get_thread(&request.destination_thread_id).await?;\nif src.owner_id != dst.owner_id || src.workspace_id != dst.workspace_id {\n    return Err(anyhow::anyhow!(\"cross-owner/cross-workspace mail is not permitted\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Only pass thread ids created by the same runtime owner/workspace","Do not mix ids across environments (prod ids in a dev runtime)","After workspace migrations, verify both thread records were re-keyed consistently"],"tags":["agent-mail","authorization","multi-tenant","workspace"],"backgroundTag":"authorization-denied","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}