Tony’s morning briefing was failing every weekday at 6:30am. Discord showed
⚠️ ✉️ Message failed. The job had consecutiveErrors: 2.
The logs at the time of the scheduled run:
message failed: Unknown target "adam" for Discord. Hint: <channelId|user:ID|channel:ID>
message failed: Action send requires a target.
Tony was trying to send to a target named "adam" — not a valid Discord
target. Discord targets need to be channel:ID, user:ID, or a raw channel
ID snowflake. A name doesn’t work.
The Problem
The cron job was configured with "mode": "announce" in the delivery block.
That means OpenClaw automatically takes whatever the agent outputs and posts it
to the configured channel. The agent just needs to produce a response.
But the job prompt ended with: “Send the formatted briefing.”
Tony read that literally. It reached for a send tool and tried to pass
"adam" as the target — the only hint it had about who should receive the
message. When that failed, it tried again with no target at all. Two tool
errors, a failed job status, and no briefing in #chat.
Separation of concerns was correct in the config. It broke in the prompt.
Why This Happens
In OpenClaw, announce delivery mode is a runtime concern. After the
agentTurn session completes, the gateway takes the agent’s text response and
routes it to the configured channel. The agent doesn’t need to know the channel
ID, the platform, or anything about delivery.
When the prompt says “send,” the agent treats delivery as part of its job. It
goes looking for a way to send — finds a send action — and tries to fill in
the target from whatever context it has. In this case, that context was the
word “Adam” from its identity files. Not a channel ID. Not a valid Discord target.
Two layers. Two jobs. The config layer handles routing. The agent layer handles content.
The Fix
Update the cron job prompt in cron/jobs.json:
Before:
"message": "Run the morning briefing. Send the formatted briefing."
After:
"message": "Run the morning briefing. Reply with the formatted briefing as your response — do not call send or any messaging tool."
The delivery block was already correct:
"delivery": {
"mode": "announce",
"channel": "discord",
"to": "channel:YOUR_CHANNEL_ID"
}
With announce mode, the agent’s text response is the message. The runtime
posts it. Done.
Key Takeaway
When a scheduled job uses announce delivery mode, the agent’s job ends at
producing output. The runtime handles the rest. If your prompt says “send
something,” the agent will try to — and it probably doesn’t have enough context
to do it correctly.
Write prompts that describe what to produce. Write configs that describe where it goes.
FAQ
My OpenClaw scheduled job says message failed but the session completed.
What’s happening?
The agent turn completed, but a send tool call inside the session failed. If
your job uses announce delivery mode, the agent shouldn’t be calling send —
the runtime handles posting after the session ends. Remove any “send”
instruction from your job prompt.
What’s the difference between announce mode and having the agent call
send?
Announce mode automatically posts the agent’s text response to the configured
channel after the session completes. If the agent also calls send, it’s
attempting a second delivery — one the runtime isn’t controlling, with a target
the agent has to guess. Pick one delivery pattern per job.
What are valid Discord targets in OpenClaw?
channel:CHANNEL_ID, user:USER_ID, or a raw channel ID snowflake. A
display name like "adam" is not valid — OpenClaw needs a Discord snowflake
ID.
My consecutiveErrors is climbing on a cron job. Where do I look first?
Check journalctl --user -u openclaw-gateway filtered to the time the job
runs. The actual tool-call errors show up there. The lastError in jobs.json
often shows only the final delivery status, not the tool-level failure that
caused it.