Skip to content

Bug: notify() drops messages when runtime sender is missing or throws instead of falling back #520

Description

@fujiwaranosai850

Problem

notify() in lib/dispatch/notify.ts is runtime-first, but if runtime exists and the channel sender is missing or throws, the notification is dropped instead of falling back to openclaw message send.

This leads to user-visible notification loss for failures like:

Cannot read properties of undefined (reading 'sendMessageTelegram')

Current behavior

The current logic is effectively:

if (runtime) {
  await runtime.channel.telegram.sendMessageTelegram(...)
  return true
}

await runCommand(["openclaw", "message", "send", ...])

wrapped in an outer try/catch.

That means:

  • CLI/OpenClaw fallback is only used when runtime is absent
  • if runtime is present but runtime.channel.telegram is undefined, or the runtime sender throws, the outer catch logs notify_error and returns false
  • the message is never handed off to OpenClaw's normal outbound delivery path

Why this is a bug

Runtime-first is reasonable for performance and for avoiding CLI/WebSocket timeout issues.

But once runtime send fails, notification delivery should still fall back to the standard OpenClaw send path. Right now the failure mode is effectively silent best-effort message loss.

For worker lifecycle notifications, if the user never sees the message, the message was effectively not sent.

Expected behavior

If runtime sender lookup fails, or runtime send throws, notify() should:

  1. log the runtime failure
  2. fall back to openclaw message send
  3. only return false if both runtime send and fallback fail

Suggested fix

Refactor sendMessage() so it:

  • safely resolves the runtime sender for the selected channel
  • uses runtime send only if that sender exists
  • on runtime error, tries CLI/OpenClaw fallback
  • logs delivery path used, for example:
    • runtime
    • cli-fallback
    • failed

Notes

This is distinct from earlier related issues:

Those explain why runtime-first exists, but they do not address the missing fallback when runtime send itself fails.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions