---
title: "Handle refusals and cut-off answers · Agent Wranglers"
url: https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review/refusals-and-truncation
updated: 2026-10-20
licence: "All rights reserved"
---

# Handle refusals and cut-off answers

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Guardrails and human review](https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review)
- Rule: Read the stop reason of every model response. Show a refusal or a cut-off answer as such, with a next step, and never as a complete answer.
- How: Each provider reports why output ended. The Anthropic API sets `stop_reason`, with values such as `end_turn`, `max_tokens`, `refusal` and `model_context_window_exceeded`. The OpenAI Responses API sets `status` to `incomplete` with `incomplete_details.reason`, and puts a refusal in a content item of type `refusal`. The Gemini API sets `finishReason`. Map them in the gateway to a few values of your own, such as complete, cut off, refused and filtered. Tool calls and paused turns, such as `tool_use` and `pause_turn`, are not endings, so the tool loop handles them. Map any value you do not know to `unknown`, log it and treat it as not complete. For a cut-off answer, retry once with a higher limit or show it marked as incomplete. For a refusal, show a fixed message and a way to reach a person. Count each kind per feature.
- Check: A unit test with a fake provider returns each stop reason in turn, plus one invented value. The gateway maps each one to the right value and the invented one to `unknown`. The UI test shows no cut-off answer without its incomplete label.

Example:

```ts
type Ending = 'complete' | 'cut_off' | 'refused' | 'filtered' | 'unknown';

const ENDINGS: Record<string, Ending> = {
  end_turn: 'complete', stop_sequence: 'complete', max_tokens: 'cut_off', model_context_window_exceeded: 'cut_off', refusal: 'refused',
  completed: 'complete', max_output_tokens: 'cut_off', content_filter: 'filtered',
  STOP: 'complete', MAX_TOKENS: 'cut_off', SAFETY: 'filtered', RECITATION: 'filtered', PROHIBITED_CONTENT: 'filtered', BLOCKLIST: 'filtered', SPII: 'filtered',
};

export function endingOf(providerReason: string): Ending {
  const ending = ENDINGS[providerReason];
  if (!ending) log.warn({ event: 'ai.unknown_ending', providerReason });
  return ending ?? 'unknown';
}
```

```python
ENDINGS = {
    "end_turn": "complete", "stop_sequence": "complete", "max_tokens": "cut_off",
    "model_context_window_exceeded": "cut_off", "refusal": "refused",
    "completed": "complete", "max_output_tokens": "cut_off", "content_filter": "filtered",
    "STOP": "complete", "MAX_TOKENS": "cut_off", "SAFETY": "filtered", "RECITATION": "filtered",
    "PROHIBITED_CONTENT": "filtered", "BLOCKLIST": "filtered", "SPII": "filtered",
}

def ending_of(provider_reason: str) -> str:
    ending = ENDINGS.get(provider_reason)
    if ending is None:
        log.warning("ai.unknown_ending", provider_reason=provider_reason)
    return ending or "unknown"
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers ai features playbook: https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review/refusals-and-truncation`.

- Previous: [Check output before anyone sees it](https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review/output-checks)
- Next: [Let a person decide what matters](https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review/human-decides)
- [Guardrails and human review](https://agentwranglers.se/playbook/ai-features/guardrails-and-human-review)
