deepseek-ai/deepseek-harness · warning

approval response rejected: ${receipt.reason}

Error message

approval response rejected: ${receipt.reason}

What it means

PendingApproval.answer() delivers the user's allowed-once/rejected decision through the carrier wait's respond(). The host reconciles approval responses against its pending set; if the response arrives too late or duplicates another answer, the receipt comes back accepted:false with a reason, which answer() rethrows as 'approval response rejected: <reason>'. Panel removal stays frame-driven via the broadcast approval/resolved event.

Source

Thrown at packages/client/ui-conversation/src/client/contract/slots.ts:720

  /** The paired tool call's id when the ask names one (command-line lookup key), forwarded from the carrier payload. */
  get callId(): string | undefined {
    return this.wait.payload.callId
  }

  /**
   * Deliver the user's decision; a rejected carrier receipt throws. Panel
   * removal stays frame-driven: the broadcast `approval/resolved` settles the
   * wait and drops it from the pending list.
   * @param outcome - the only two client-answerable outcomes.
   */
  async answer(outcome: 'allowed-once' | 'rejected'): Promise<void> {
    const receipt = await this.wait.respond({
      ok: true,
      value: { sessionId: this.wait.sessionId, approvalId: this.wait.payload.approvalId, outcome },
    })
    if (!receipt.accepted) {
      throw new Error(`approval response rejected: ${receipt.reason}`)
    }
  }
}

/**
 * Full approval-composer props: the framework runtime share (chain currency +
 * session/global standard kit) plus the chain `matched` share — the entry's
 * selector result, already narrowed to the approval carrier — plus the
 * standard locale seat. No injected share: the carrier plus the domain face
 * above carry the whole behavior surface; the paired command line derives
 * from useSession in-component.
 */
export type ApprovalComposerProps =
  PropsRuntime<'conversation.composer'> & { matched: ApprovalWait } & PropsLocale<'conversation'>

/** In-memory reader position resilient to transcript width reflow. */
export interface ChatScrollPosition {
  /** Stable rendered node/call identity nearest the visible reading edge. */

View on GitHub (pinned to b150a551b8)

Solutions

  1. Treat as benign when the approval already resolved — check the pending list or the approval/resolved event before reporting an error
  2. Latch the Allow/Reject buttons one-shot so a second click cannot fire answer() twice
  3. If the reason says the wait never existed, resync session state to rebuild the pending approvals view
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await approval.answer(outcome)
} catch (error) {
  if (approvalAlreadyResolved(approval.key)) return // benign double-answer race
  throw error
}

Prevention

When it happens

Trigger: Answering an approval that was already settled elsewhere: double-clicking Allow/Reject, a second panel answering the same approvalId, the host or another client (CLI plus web) answering first, or the approval/resolved broadcast landing while the user's click is still in flight.

Common situations: Race between the resolved-event broadcast removing the panel and the user's click; retry logic that re-fires answer() after a timeout; multi-surface setups where either surface can decide.

Related errors


AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24). Data as JSON: /api/errors/909b88475a98edac. Report an issue: GitHub.