{"record":{"id":"f317d9231fb6a95f","repo":"Hmbown/CodeWhale","slug":"agent-mail-source-and-destination-threads-must-dif","errorCode":null,"errorMessage":"Agent Mail source and destination threads must differ","messagePattern":"Agent Mail source and destination threads must differ","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":3610,"sourceCode":"        if agent_mail_looks_like_raw_transcript(&request.summary) {\n            bail!(\"Agent Mail accepts a bounded handoff summary, not a raw transcript\");\n        }\n        request.summary = sanitize_agent_mail_text(&request.summary, MAX_AGENT_MAIL_SUMMARY_BYTES);\n        request.sender.display_label = sanitize_agent_mail_text(\n            &request.sender.display_label,\n            codewhale_protocol::agent_mail::MAX_AGENT_MAIL_DISPLAY_LABEL_BYTES,\n        );\n        for evidence in &mut request.evidence {\n            if let Some(label) = evidence.label.as_mut() {\n                *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            );","sourceCodeStart":3592,"sourceCodeEnd":3628,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L3592-L3628","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\nlet request = AgentMailSendRequest {\n    source_thread_id: thread_id.clone(),\n    destination_thread_id: thread_id.clone(), // bug: same thread\n    ..\n};\n\n// after\nlet request = AgentMailSendRequest {\n    source_thread_id: thread_id.clone(),\n    destination_thread_id: peer_thread_id.clone(),\n    ..\n};","handlingStrategy":"validation","validationCode":"if request.source_thread_id == request.destination_thread_id {\n    return Err(anyhow::anyhow!(\"agent mail requires distinct source and destination threads\"));\n}\nmanager.queue_agent_mail(request).await?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["agent-mail","request-validation","self-reference"],"backgroundTag":"request-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}