Skip to content

feat(cron): add accountId support to delivery config#10847

Closed
jcardama wants to merge 4 commits intoopenclaw:mainfrom
jcardama:feat/cron-delivery-accountId
Closed

feat(cron): add accountId support to delivery config#10847
jcardama wants to merge 4 commits intoopenclaw:mainfrom
jcardama:feat/cron-delivery-accountId

Conversation

@jcardama
Copy link

@jcardama jcardama commented Feb 7, 2026

Summary

Add optional accountId field to cron delivery configuration, allowing cron jobs to target a specific channel account for delivery.

Problem

When multiple bot accounts are configured on the same channel (e.g., a main Telegram bot and an alerts bot), cron job delivery always uses the account inferred from the session store (the last-used account). There is no way to explicitly route cron notifications to a different account.

For example, with two Telegram bots configured:

telegram:
  accounts:
    main:
      botToken: "..."
    alerts:
      botToken: "..."

A cron job with delivery: { mode: "announce", channel: "telegram", to: "123456" } would always deliver via whichever account was last used in the main session, with no way to force delivery through the alerts account.

Solution

Add an optional accountId field to the delivery config:

{
  "delivery": {
    "mode": "announce",
    "channel": "telegram",
    "to": "123456",
    "accountId": "alerts"
  }
}

When accountId is set, it overrides the account inferred by resolveDeliveryTarget() from the session store.

Changes

src/cron/types.ts

  • Add accountId?: string to CronDelivery type (flows to CronDeliveryPatch via Partial<>)

src/gateway/protocol/schema/cron.ts

  • Add accountId: Type.Optional(NonEmptyString) to CronDeliverySchema and CronDeliveryPatchSchema

src/cron/delivery.ts

  • Add accountId?: string to CronDeliveryPlan type
  • Extract accountId from delivery object in resolveCronDeliveryPlan()
  • Include it in the returned plan when source is "delivery"

src/cron/isolated-agent/run.ts

  • After resolveDeliveryTarget(), override resolvedDelivery.accountId when deliveryPlan.accountId is set
  • This ensures the explicit config takes precedence over session-inferred account

src/cron/normalize.ts

  • Preserve accountId through coerceDelivery() normalization (trim + validate like other string fields)

Backward Compatible

  • accountId is optional everywhere — existing jobs without it behave exactly as before
  • No migration needed for existing jobs.json files
  • The field is simply ignored if the specified account doesn't exist (existing error handling in resolveOutboundTarget covers this)

Greptile Overview

Greptile Summary

  • Adds an optional delivery.accountId field to cron job config/types and gateway schemas so users can select a specific channel account for outbound delivery.
  • Normalizes/trims delivery.accountId in cron job input parsing.
  • Threads the new field into the cron isolated agent delivery plan and overrides the resolved delivery target’s accountId before sending.
  • Overall fits into the existing delivery-target resolution flow (session-inferred defaults + explicit overrides) but currently overrides the account after target validation.

Confidence Score: 3/5

  • This PR is close to mergeable, but it changes delivery target selection in a way that can bypass existing target/account validation.
  • The schema/type additions and normalization are straightforward, but overriding accountId after resolveDeliveryTarget() means the new accountId is not validated/docked via resolveOutboundTarget(), so misconfigured accountId may only fail later or behave inconsistently with existing error handling.
  • src/cron/isolated-agent/run.ts; src/cron/isolated-agent/delivery-target.ts

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Allow cron jobs to target a specific channel account (e.g. alerts bot
vs main bot) via an optional `accountId` field in the delivery config.

Without this, cron delivery always uses the account inferred from the
session store (last-used account), making it impossible to route
notifications to a different bot account on the same channel.

Changes:
- Add `accountId` to CronDelivery/CronDeliveryPatch types
- Add `accountId` to CronDeliverySchema/CronDeliveryPatchSchema
- Extract `accountId` in resolveCronDeliveryPlan()
- Override resolvedDelivery.accountId when delivery plan has explicit accountId
- Preserve `accountId` through coerceDelivery normalization
@openclaw-barnacle openclaw-barnacle bot added app: web-ui App: web-ui gateway Gateway runtime labels Feb 7, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 50 to +52
);
const deliveryTo = normalizeTo((delivery as { to?: unknown } | undefined)?.to);
const deliveryAccountId = normalizeTo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accountId ignored for payload-only jobs

resolveCronDeliveryPlan() only reads accountId from job.delivery, but jobs without an explicit delivery object (legacy/payload-driven configs) can’t specify an account override even if they can specify channel/to via payload. If that’s not intended, this change is incomplete: either document that accountId only works when delivery is present, or add accountId support to the payload schema/types and include it in the payload-sourced plan.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/delivery.ts
Line: 50:52

Comment:
**accountId ignored for payload-only jobs**

`resolveCronDeliveryPlan()` only reads `accountId` from `job.delivery`, but jobs without an explicit `delivery` object (legacy/payload-driven configs) can’t specify an account override even if they can specify `channel`/`to` via payload. If that’s not intended, this change is incomplete: either document that `accountId` only works when `delivery` is present, or add `accountId` support to the payload schema/types and include it in the `payload`-sourced plan.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — accountId is delivery-only by design.

The payload fields (deliver, channel, to) are legacy. The normalizer (normalizeCronJobCreate with applyDefaults: true) already auto-promotes them into a delivery object for any new job, so in practice all new jobs get a delivery block where accountId can be set.

Added a code comment in 36651eb explaining the scope.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 7, 2026

Additional Comments (1)

src/cron/isolated-agent/run.ts
Account override bypasses validation

resolveDeliveryTarget() validates/docks the target via resolveOutboundTarget() using the session-inferred accountId. Overriding resolvedDelivery.accountId after that means an invalid/missing delivery.accountId can slip through without triggering resolvedDelivery.error, and delivery may attempt to send with a non-existent account. Fix by passing accountId into resolveDeliveryTarget() (and having it validate via resolveOutboundTarget()), or re-run resolveOutboundTarget() after overriding to preserve the existing error handling behavior.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/isolated-agent/run.ts
Line: 276:278

Comment:
**Account override bypasses validation**

`resolveDeliveryTarget()` validates/docks the target via `resolveOutboundTarget()` using the session-inferred `accountId`. Overriding `resolvedDelivery.accountId` *after* that means an invalid/missing `delivery.accountId` can slip through without triggering `resolvedDelivery.error`, and delivery may attempt to send with a non-existent account. Fix by passing `accountId` into `resolveDeliveryTarget()` (and having it validate via `resolveOutboundTarget()`), or re-run `resolveOutboundTarget()` after overriding to preserve the existing error handling behavior.

How can I resolve this? If you propose a fix, please make it concise.

- Remove redundant `as string` cast in coerceDelivery (lint error)
- Add comment explaining accountId is delivery-only by design
  (legacy payload fields are auto-promoted to delivery on creation)
Instead of overriding resolvedDelivery.accountId after the fact (which
bypasses resolveOutboundTarget validation), pass accountId into
resolveDeliveryTarget so it flows through the existing validation path.

This ensures an invalid/non-existent accountId triggers
resolvedDelivery.error like any other bad delivery target.
@jcardama
Copy link
Author

jcardama commented Feb 7, 2026

Additional Comments (1)

src/cron/isolated-agent/run.ts Account override bypasses validation

resolveDeliveryTarget() validates/docks the target via resolveOutboundTarget() using the session-inferred accountId. Overriding resolvedDelivery.accountId after that means an invalid/missing delivery.accountId can slip through without triggering resolvedDelivery.error, and delivery may attempt to send with a non-existent account. Fix by passing accountId into resolveDeliveryTarget() (and having it validate via resolveOutboundTarget()), or re-run resolveOutboundTarget() after overriding to preserve the existing error handling behavior.
Prompt To Fix With AI

Addressed in d1b4f4a — instead of overriding accountId after resolveDeliveryTarget(), it's now passed into the function so it flows through resolveOutboundTarget() validation. An invalid/non-existent accountId will now correctly trigger resolvedDelivery.error.

@tyler6204
Copy link
Member

Superseded by #11641 (merge commit: 8fae55e). Closing to reduce duplicate PR noise. Please open a new PR only if there is additional scope beyond this fix.

@tyler6204 tyler6204 closed this Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments