Skip to content

Cron delivery fails for custom channel plugins: 'Outbound not configured' despite proper registration #1530

@wca4a

Description

@wca4a

Problem

Cron jobs with --deliver --channel tlon fail with error:

Error: Outbound not configured for channel: tlon

However, regular Tlon messages work perfectly - the plugin successfully sends/receives messages in normal gateway operation.

Evidence

Regular Messages Work ✅

Gateway logs show successful Tlon message delivery:

[tlon] Delivered AI reply to ~malmur-halmex
[tlon] AI dispatch completed for ~malmur-halmex (total: 6915ms)

Cron Delivery Fails ❌

Cron run logs show:

{
  "status": "error",
  "error": "Error: Outbound not configured for channel: tlon",
  "summary": "[AI successfully generated summary]",
  "durationMs": 32907
}

The AI agent runs successfully and generates output, but delivery fails at the outbound stage.

Setup

Cron Job Configuration:

clawdbot cron add \
  --name "Daily tweet summary to Tlon" \
  --cron "0 9 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Use bird to fetch tweets..." \
  --deliver \
  --channel tlon \
  --to "dm/~malmur-halmex"

Plugin Structure:

  • Location: /Users/williamarzt/.clawdbot/extensions/tlon/
  • Registered via: api.registerChannel({ plugin: tlonPlugin })
  • Has both sendText and sendMedia functions
  • Has chunker with correct (text, limit) signature
  • Has deliveryMode: "direct"

Config:

{
  "channels": {
    "tlon": {
      "enabled": true,
      "ship": "sitrul-nacwyl",
      "code": "...",
      "url": "https://sitrul-nacwyl.tlon.network"
    }
  },
  "plugins": {
    "entries": {
      "tlon": { "enabled": true }
    }
  }
}

Investigation Findings

Code Path Difference (from Discord help)

Discord helper suggested:

Sounds like cron uses a different outbound resolution path than regular messages. The cron system might look up channels differently — maybe by string name rather than the registered plugin instance. Worth checking how other channel plugins (WhatsApp, Telegram) handle cron delivery, or dig into packages/gateway/src/cron to see how it resolves outbound channels. Might need to register Tlon in a specific way for cron to find it.

Verified Plugin Registration

Plugin exports (index.js):

export const tlonPlugin = {
  id: "tlon",
  meta: { id: "tlon", label: "Tlon", /* ... */ },
  capabilities: { chatTypes: ["direct", "group"], media: false },
  outbound: {
    deliveryMode: "direct",
    chunker: (text, limit) => [text],
    textChunkLimit: 10000,
    sendText: async ({ cfg, to, text, accountId }) => { /* implementation */ },
    sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => { /* implementation */ }
  },
  // ... config, status, gateway adapters
};

const plugin = {
  id: "tlon",
  name: "Tlon",
  description: "Tlon/Urbit channel plugin",
  register(api) {
    api.registerChannel({ plugin: tlonPlugin });
  }
};

export default plugin;

clawdbot.plugin.json:

{
  "id": "tlon",
  "channels": ["tlon"],
  "configSchema": { "type": "object", "additionalProperties": false, "properties": {} }
}

package.json:

{
  "name": "@clawdbot/tlon",
  "type": "module",
  "main": "index.js",
  "clawdbot": { "extensions": ["./index.js"] }
}

Delivery Code Path

From /opt/homebrew/lib/node_modules/clawdbot/dist/infra/outbound/deliver.js:

async function createChannelHandler(params) {
    const outbound = await loadChannelOutboundAdapter(params.channel);
    if (!outbound?.sendText || !outbound?.sendMedia) {
        throw new Error(`Outbound not configured for channel: ${params.channel}`);
    }
    // ...
}

From /opt/homebrew/lib/node_modules/clawdbot/dist/channels/plugins/outbound/load.js:

export async function loadChannelOutboundAdapter(id) {
    const registry = getActivePluginRegistry();
    ensureCacheForRegistry(registry);
    const cached = cache.get(id);
    if (cached) return cached;
    
    const pluginEntry = registry?.channels.find((entry) => entry.plugin.id === id);
    const outbound = pluginEntry?.plugin.outbound;
    if (outbound) {
        cache.set(id, outbound);
        return outbound;
    }
    return undefined;
}

Issue: loadChannelOutboundAdapter("tlon") returns undefined in cron context, but the plugin clearly has outbound defined and works in regular gateway context.

Hypothesis

The plugin registry might be in a different state when cron's isolated agent session runs vs when regular gateway message handling runs. Possible issues:

  1. Plugin registry not populated in isolated sessions - Cron isolated sessions might not have access to the full plugin registry
  2. Timing issue - Plugin might not be fully registered when cron job starts
  3. Different registry instances - Cron might be using a different global registry instance
  4. Built-in vs external plugin handling - Built-in plugins (telegram, signal) might be registered differently than external plugins loaded from ~/.clawdbot/extensions/

System Info

  • Clawdbot version: 2026.1.22
  • OS: macOS 15.2 (Darwin 25.2.0)
  • Node: v25.3.0
  • Plugin location: ~/.clawdbot/extensions/tlon/
  • Plugin source: Custom external plugin (not bundled)

Expected Behavior

Cron jobs should be able to deliver to custom channel plugins the same way regular message flow can.

Actual Behavior

Cron delivery fails with "Outbound not configured for channel: tlon" despite:

  • Plugin being properly registered
  • Having both sendText and sendMedia functions
  • Regular (non-cron) messages working perfectly
  • Plugin being loaded and active in gateway

Request

Could you investigate how cron's loadChannelOutboundAdapter() resolves custom channel plugins vs built-in ones? It seems there's a difference in the plugin registry lookup path between regular message delivery and cron delivery.

Workaround Attempted

Added both sendText and sendMedia to satisfy the check in deliver.js:16, but this didn't resolve the issue since the problem is earlier in the lookup chain (plugin not found in registry).


Related Discord discussion: [link if available]
Plugin repo: https://github.com/wca4a/clawdbot-tlon-plugin

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions