Problem
`deliverOutboundPayloads()` returns `OutboundDeliveryResult[]`, but an empty array (`[]`) is ambiguous — it can mean:
- Hook cancellation: A `message_sending` hook cancelled all payloads via `{ cancel: true }`. This is intentional policy, not a failure.
- Delivery failure: The send was attempted but produced zero results (e.g. channel plugin returned nothing).
Callers have no way to distinguish these cases. This matters for #53961 and #57755, which add delivery status tracking — both currently report hook cancellations as `deliveryStatus.succeeded: false`, which is incorrect.
Second issue: partial delivery on non-bestEffort throw
When `bestEffortDeliver` is false and delivery throws mid-stream, `deliverOutboundPayloadsCore()` may have already sent earlier payloads (the results array is built incrementally). The thrown error carries no information about what was already delivered, so callers report `succeeded: false` even though some messages reached the channel. Retrying based on that status can duplicate the already-delivered prefix.
Proposed fix
Enrich the return type to carry outcome metadata:
```typescript
interface DeliveryOutcome {
results: OutboundDeliveryResult[];
/** True when all payloads were cancelled by message_sending hooks. /
cancelledByHook: boolean;
/* Number of payloads that were successfully sent before a throw (non-bestEffort). */
sentBeforeError?: number;
}
```
This would let callers distinguish:
- `results.length === 0 && cancelledByHook` → policy suppression, not failure
- `sentBeforeError > 0` on catch → partial delivery, don't retry blindly
Impact
Scope
Changes are in `src/infra/outbound/deliver.ts` (`deliverOutboundPayloadsCore` and the outer wrapper). All callers of `deliverOutboundPayloads` would need to handle the new return shape, but the change is backward-compatible if structured as `results & { cancelledByHook?: boolean }`.
Problem
`deliverOutboundPayloads()` returns `OutboundDeliveryResult[]`, but an empty array (`[]`) is ambiguous — it can mean:
Callers have no way to distinguish these cases. This matters for #53961 and #57755, which add delivery status tracking — both currently report hook cancellations as `deliveryStatus.succeeded: false`, which is incorrect.
Second issue: partial delivery on non-bestEffort throw
When `bestEffortDeliver` is false and delivery throws mid-stream, `deliverOutboundPayloadsCore()` may have already sent earlier payloads (the results array is built incrementally). The thrown error carries no information about what was already delivered, so callers report `succeeded: false` even though some messages reached the channel. Retrying based on that status can duplicate the already-delivered prefix.
Proposed fix
Enrich the return type to carry outcome metadata:
```typescript
interface DeliveryOutcome {
results: OutboundDeliveryResult[];
/** True when all payloads were cancelled by message_sending hooks. /
cancelledByHook: boolean;
/* Number of payloads that were successfully sent before a throw (non-bestEffort). */
sentBeforeError?: number;
}
```
This would let callers distinguish:
Impact
Scope
Changes are in `src/infra/outbound/deliver.ts` (`deliverOutboundPayloadsCore` and the outer wrapper). All callers of `deliverOutboundPayloads` would need to handle the new return shape, but the change is backward-compatible if structured as `results & { cancelledByHook?: boolean }`.