zeroclaw-labs/zeroclaw · error · anyhow::Error
iMessage send failed: {stderr}
Error message
iMessage send failed: {stderr} What it means
IMessageChannel::send runs an osascript AppleScript that tells Messages.app to send the text; when the process exits non-zero, the captured stderr becomes the error. The AppleScript itself escaped both message and target against CWE-78 injection, so a non-zero exit is environmental: Messages.app state, AppleScript/Automation permissions, or an unreachable recipient — the stderr text carries the real reason.
Source
Thrown at crates/zeroclaw-channels/src/imessage.rs:180
let escaped_target = escape_applescript(&message.recipient);
let script = format!(
r#"tell application "Messages"
set targetService to 1st account whose service type = iMessage
set targetBuddy to participant "{escaped_target}" of targetService
send "{escaped_msg}" to targetBuddy
end tell"#
);
let output = tokio::process::Command::new("osascript")
.arg("-e")
.arg(&script)
.output()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("iMessage send failed: {stderr}");
}
Ok(())
}
async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> anyhow::Result<()> {
::zeroclaw_log::record!(
INFO,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note),
"iMessage channel listening (AppleScript bridge)..."
);
// Query the Messages SQLite database for new messages
// The database is at ~/Library/Messages/chat.db
let db_path = UserDirs::new()
.map(|u| u.home_dir().join("Library/Messages/chat.db"))
.ok_or_else(|| {
::zeroclaw_log::record!(View on GitHub (pinned to 88bb9c8533)
Solutions
- Reproduce manually to see the real AppleScript error: run the same 'tell application "Messages" ... send ...' snippet via osascript -e in the same terminal/session
- Grant Automation permission: System Settings > Privacy & Security > Automation > [your terminal/zeroclaw] > Messages
- Open Messages.app, sign into iCloud, confirm iMessage is active, and verify the recipient works from the GUI
- If the target is SMS-only, route through a different channel — the AppleScript bridge only does iMessage
Defensive patterns
Strategy: try-catch
Try / catch
match channel.send(&msg).await {
Err(e) if e.to_string().contains("iMessage send failed") => {
// e carries osascript stderr verbatim: match on 'Not authorized' (Automation perm),
// 'not signed in' (iCloud), or registration errors; route message to a fallback channel
}
other => other?,
} Prevention
- Pre-authorize osascript: once from a terminal run any Messages AppleScript and accept the Automation prompt
- Check Messages.app is signed in and the recipient is iMessage-registered before relying on this bridge
- Treat stderr text as the diagnosis — reproduce the failing script manually before changing code
When it happens
Trigger: osascript exits non-zero: Messages.app not signed into an iCloud account with iMessage enabled; macOS Automation permission not granted to the process invoking osascript ('Not authorized to send Apple events to Messages'); recipient not registered with iMessage; Messages.app not running/first-run dialogs pending.
Common situations: Fresh mac that never opened Messages.app; zeroclaw launched from a terminal or launchd agent missing in System Settings > Privacy & Security > Automation; recipient is an SMS-only number; macOS update reset TCC permissions.
Related errors
- Invalid iMessage target: must be a phone number (+1234567890
- Messages database not found at {}. Ensure Messages.app is se
- channel does not support room invites
- ACP returned unknown permission optionId: {other}
- non-success status {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/f22090e64a24e1fe.
Report an issue: GitHub.