nanocoai/nanoclaw · warning
reject-with-reason: cannot reach approver, finalizing plain
Error message
reject-with-reason: cannot reach approver, finalizing plain reject
What it means
A reject-with-reason flow was armed, but the approver cannot be reached: either no DM channel exists for the approver user (ensureUserDm returned null) or no delivery adapter is registered (getDeliveryAdapter returned null). The system degrades gracefully by finalizing the rejection without capturing a reason.
Source
Thrown at src/modules/approvals/reason-capture.ts:92
} catch {
return '';
}
}
/**
* Begin the reject-with-reason hold for an approval the admin chose not to
* finalize outright. Prompts the admin's DM, then parks the row and arms
* capture. If we can't reach the admin (no DM, no adapter, delivery throws) we
* finalize a plain reject immediately rather than strand the requesting agent.
*/
export async function armReasonCapture(approval: PendingApproval, session: Session, userId: string): Promise<void> {
const expiresAt = new Date(Date.now() + REASON_CAPTURE_WINDOW_MS).toISOString();
if (!(await markApprovalAwaitingReason(approval.approval_id, expiresAt))) return;
const dm = userId ? await ensureUserDm(userId) : null;
const adapter = getDeliveryAdapter();
if (!dm || !adapter) {
log.warn('reject-with-reason: cannot reach approver, finalizing plain reject', {
approvalId: approval.approval_id,
userId,
hasDm: Boolean(dm),
hasAdapter: Boolean(adapter),
});
await finalizeReject(approval, session, userId, undefined, 'awaiting_reason');
return;
}
try {
await adapter.deliver(dm.channel_type, dm.platform_id, null, 'chat-sdk', JSON.stringify({ text: PROMPT_TEXT }));
} catch (err) {
log.error('reject-with-reason: reason prompt delivery failed, finalizing plain reject', {
approvalId: approval.approval_id,
err,
});
await finalizeReject(approval, session, userId, undefined, 'awaiting_reason');
return;View on GitHub (pinned to 294ef2aee8)
Solutions
- Wire a DM channel for the approver (e.g. run /init-first-agent) so ensureUserDm can resolve a DM
- Verify the delivery adapter for the approver's channel is registered in the channel registry
- Check the user_dms table for a stale/missing entry for the approver user id
Defensive patterns
Strategy: validation
Validate before calling
const dm = userId ? await ensureUserDm(userId) : null;
const adapter = getDeliveryAdapter();
if (!dm || !adapter) {
// degrade before arming: reject without reason flow
await finalizeReject(approval, session, userId, undefined, 'no_dm');
return;
} Prevention
- Wire a DM channel for approvers at install time (/init-first-agent)
- Monitor logs for 'cannot reach approver' to catch missing DM wiring early
When it happens
Trigger: An approver clicks 'reject with reason' but the approver has no cached DM (user_dms) and no DM-capable channel adapter is registered, so the reason prompt cannot be delivered.
Common situations: Fresh install with no DM channel wired to the approver; channel adapters not loaded; approver identity exists only on a channel that was removed.
Related errors
- --approver is required
- Ignoring unauthorized approval response
- No approval handler registered — row dropped
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/0ca67a09fb5bdf8c.
Report an issue: GitHub.