Claude Opus 5.5: el riesgo no es el modelo, son sus guardarraíles
Lanzas la migración un jueves por la noche con Claude Opus 5.5. Cuarenta y dos paquetes, un monorepo que nadie ha tocado desde 2023, el agente corriendo en un runner con presupuesto para ocho horas.
Viernes por la mañana abres el log. Veintiséis paquetes migrados. El veintisiete, vacío. No hay stack trace. No hay catch que haya saltado. No hay un solo 4xx en las métricas del runner.
Lo que hay es una respuesta HTTP 200, perfectamente válida, con el array content vacío.
Y tú buscando durante hora y media un bug que no existe.
En corto: Claude Opus 5.5 lleva el mismo sistema de clasificadores de seguridad que Fable 5.1, Fable 5 y Opus 5, y cuando uno de ellos declina una petición no recibes un error: recibes un HTTP 200 con stop_reason: "refusal" y content vacío. Si tu código solo maneja códigos de error, un flujo agéntico largo se corta en silencio a mitad. El arreglo cabe en dos líneas —el parámetro fallbacks, en beta— pero no está disponible en Amazon Bedrock, Google Cloud Vertex AI, Microsoft Foundry ni en la API de lotes.
¿Qué es un refusal en Claude Opus 5.5 y por qué llega como HTTP 200?
Un refusal es una respuesta HTTP 200 en la que un clasificador de seguridad de Claude ha declinado la petición: stop_reason vale "refusal" y el array content llega vacío. Un objeto stop_details acompaña a esa respuesta y nombra la categoría de política que saltó.
No es una excepción. No es un 400. No es un 403. Es exactamente la misma forma de respuesta que usas para leer un resultado bueno, con el contenido quitado.
Esto aplica a Claude Fable 5.1, Fable 5, Opus 5.5 y Opus 5: los cuatro llevan clasificadores que pueden declinar una petición, según la documentación de refusals de Anthropic. Y en las dos categorías más agresivas, Anthropic sitúa a Opus 5.5 —lanzado el 22 de septiembre de 2026— al nivel del modelo más restringido de su catálogo: "Because Opus 5.5 is comparable to Claude Mythos 5.1 in biology and cybersecurity, we're deploying it with safeguards similar to those on Claude Fable 5.1", dice la nota de lanzamiento.
Lo miden contra un modelo y le ponen los frenos de otro.
Así se ve una respuesta declinada — es el ejemplo de la documentación, con el modelo cambiado a Opus 5.5:
{
"id": "msg_01XFUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-opus-5-5",
"content": [],
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request was declined because it could enable cyber harm."
},
"usage": {
"input_tokens": 412,
"output_tokens": 0
}
}
Fíjate en content: []. Si tu código hace response.content[0].text, ahí revienta con un TypeError a doscientos kilómetros del sitio donde está el problema real. Y si lo haces con optional chaining, te devuelve undefined y sigue como si nada, que es peor.
El guard son cinco líneas, y van antes de tocar el contenido:
if (response.stop_reason === 39;refusal39;) {
logger.warn(39;refusal39;, { category: response.stop_details?.category ?? 39;unknown39; })
throw new RefusalError(response.stop_details)
}
const text = response.content[0].text
Las 5 categorías de refusal: cyber, bio, frontier_llm, reasoning_extraction y general_harms
stop_details.category nombra qué guardarraíl ha saltado. Son cinco, y la columna de la derecha es la que conviene leer despacio:
category |
Qué la dispara | Por qué te puede tocar sin buscarlo |
|---|---|---|
cyber |
Malware, desarrollo de exploits | La doc admite que el trabajo legítimo de ciberseguridad también la dispara. Un parser de entrada, un sanitizador, un test de inyección |
bio |
Métodos de laboratorio peligrosos | Igual: "Beneficial life sciences work can also trigger this category" |
frontier_llm |
Ayudar a desarrollar modelos competidores | Restringido por los términos comerciales. Trabajo normal de machine learning también la dispara |
reasoning_extraction |
Pedirle que reproduzca su razonamiento interno en el texto | Si tu prompt dice "explica paso a paso cómo has llegado ahí", estás en zona gris |
general_harms |
Cualquier otra área de la política de uso | El cajón de sastre. También puede saltar con trabajo benigno |
Y un detalle que no está en ninguna tabla: category y explanation pueden venir null. La documentación avisa de que ese null es un valor normal y permanente, no un hueco por rellenar. Es decir: puedes recibir un rechazo sin saber de qué categoría.
explanation viene en texto legible, pero la doc es explícita en que el texto no es estable: se muestra, no se parsea. Si montas lógica sobre esa cadena, se te rompe en la siguiente actualización.
Hay un cuarto campo que el JSON de arriba no muestra: recommended_model, el modelo que Anthropic sugiere para esa categoría. Es el que usa el fallback del servidor — y el que tienes que leer tú si estás en Bedrock o Vertex y te toca montarlo en cliente. Llega solo en peticiones que piden fallbacks, y la doc avisa de que es una pista, no una garantía.
Por qué un refusal rompe un flujo agéntico en silencio
Que un modelo decline una petición sensible es discutible, pero es una decisión de producto. El problema de ingeniería es otro, y es que el rechazo tiene forma de éxito.
Pasa esto:
- Tu agente lleva seis horas migrando. Va por el paquete 27.
- El diff de ese paquete toca el middleware de autenticación. El clasificador
cyberse activa. - La API devuelve 200. Tu cliente HTTP está encantado. Tu métrica de errores, plana.
- El paso 27 produce una cadena vacía, y el bucle agéntico —que confía en su propia salida— sigue adelante con eso.
Ese cuarto punto es el caro. En un bucle agéntico en producción, la salida de un paso es el contexto del siguiente. Un vacío no propaga una excepción: propaga basura.
Hay dos detalles de facturación que conviene tener claros, porque cambian cómo instrumentas esto:
- Un rechazo que llega antes de cualquier salida no se factura.
contentviene vacío y los tokens aparecen enusagepero no se cobran. Eso sí: cuenta contra tus rate limits. - Un rechazo a mitad de streaming sí se factura: los tokens de entrada y lo que ya se había emitido, a precio normal. Y la doc lo dice claro — esa salida parcial hay que descartarla, no aprovecharla.
Así que el escenario de verdad desagradable no es ninguno de los dos anteriores: es el agente que reintenta a ciegas. Los rechazos tempranos no los pagas, pero cada reintento te come rate limit, y el bucle se queda girando contra una pared invisible. Si no estás midiendo el consumo de tokens de tu agente, no te enteras hasta que llega el 429 — o hasta que abres el log a la mañana siguiente.
Cómo manejar un refusal: el parámetro fallbacks de la API de Claude (beta)
Anthropic tiene fallback en el servidor, en beta. Le pones fallbacks: "default" y la cabecera beta, y cuando el modelo primario declina, la API reintenta la misma petición en el modelo que Anthropic recomienda para esa categoría, dentro de la misma llamada:
import Anthropic from 39;@anthropic-ai/sdk39;
const client = new Anthropic()
const response = await client.beta.messages.create({
model: 39;claude-opus-5-539;,
max_tokens: 1024,
messages: [{ role: 39;user39;, content: 39;Hello, Claude39; }],
fallbacks: 39;default39;,
betas: [39;server-side-fallback-2026-07-0139;]
})
console.log(response.model) // el modelo que realmente respondió
response.model es la clave: te dice quién contestó de verdad, que no tiene por qué ser el que pediste. Para saber si el fallback llegó a entrar hay que mirar usage.iterations buscando una entrada de tipo fallback_message, y confirmarlo con que stop_reason ya no sea "refusal":
const huboFallback = (response.usage.iterations ?? [])
.some(it => it.type === 39;fallback_message39;)
const loSirvioElFallback = huboFallback && response.stop_reason !== 39;refusal39;
También puedes nombrar hasta tres modelos de fallback propios en lugar de dejar el enrutado por defecto. Y si una categoría no tiene fallback recomendado, el rechazo se mantiene: el parámetro no es un interruptor de "quítame los guardarraíles".
Dónde NO funciona fallbacks: Bedrock, Vertex, Foundry y Batches API
Aquí está la letra pequeña, y es la parte que decide tu arquitectura:
| Plataforma / modo | ¿fallbacks funciona? |
Qué hacer |
|---|---|---|
| API de Claude, petición normal | ✅ Sí, en beta | fallbacks: "default" + cabecera beta |
| Amazon Bedrock | ❌ No | Fallback en cliente, leyendo stop_details.recommended_model |
| Google Cloud Vertex AI / Microsoft Foundry | ❌ No | Igual: middleware del SDK y recommended_model |
| Message Batches API | ❌ No | El item del lote vuelve como resultado erróneo. Ojo si procesas en batch |
| HTTP crudo o retry propio | ➖ N/A | Reintento manual + fallback credit para no pagar dos veces la caché |
Ese último punto de la tabla es el que más dinero cuesta ignorar: si te montas el reintento a mano y el prompt cacheado es grande, pagas la caché dos veces. El fallback en servidor y el middleware del SDK aplican el crédito por ti.
Cuándo NO deberías meter Opus 5.5 en un flujo largo
La nota de lanzamiento vende justamente lo contrario: "handles long, sprawling jobs like codebase-wide migrations".
Pero en el hilo de Hacker News del lanzamiento —más de 1.400 puntos y cerca de 900 comentarios— hay un testimonio que va exactamente al grano de este post. Lo cuenta bushido:
"The safeguards really don't work well for a lot of long-running tasks on old code bases. A lot of my workloads last days to weeks and the single biggest risk to the workflow is random safeguards."
El mismo comentarista describe el bucle más incómodo: el propio modelo emite algo que a su clasificador no le gusta, y toca reiniciar la conversación.
raesene9, que trabaja en seguridad, es más tajante sobre por qué no los usa para su campo:
"I've found their guardrails so twitchy (especially Anthropic) that I wouldn't try to use them for even vaguely security related work."
Y kqp documenta un falso positivo que da la medida del problema: preguntó si una cita genérica rompía reglas de puntuación y se lo bloquearon. Reformular la frase para no usar la palabra "rules" lo arregló.
Tres situaciones concretas donde yo no lo pondría sin red:
- Migraciones desatendidas de días sobre código legacy. No por la calidad del modelo. Es que en un recorrido de cientos de pasos no eliges el contenido que vas a tocar: basta con llegar a una zona sensible —middleware de autenticación, criptografía, deserialización— para que el clasificador salte. Y ahí reintentar no sirve de nada, porque el mismo diff dispara el mismo clasificador. A eso se suma lo que describe
bushido, que sí es impredecible: que el guardarraíl se active sobre la salida del propio modelo. Ninguna de las dos cosas la ves hasta la mañana siguiente. - Cualquier cosa que roce seguridad, aunque sea defensiva: sanitizar entrada, revisar dependencias, escribir tests de inyección. El guardarraíl
cyberno distingue intención. - Procesamiento en lotes de contenido heterogéneo. El parámetro
fallbacksno existe en la API de lotes, así que ahí el rechazo se queda como está y el item vuelve como error.
Ojo, esto no es un argumento para usar otro modelo: los clasificadores no son exclusivos de Anthropic. Es un argumento para tratar el rechazo como un estado esperado de tu sistema, no como una anomalía.
Lo que los benchmarks de Opus 5.5 no miden
Los números del lanzamiento son buenos y no hay por qué discutirlos. En Terminal-Bench 4.0, Opus 5.5 saca un 66,4% frente al 52,3% de Opus 5 — y por encima de GPT-6 Astra (57,9%) y de Fable 5.1 (55,8%).
| Claude Opus 5.5 | Claude Opus 5 | |
|---|---|---|
| Entrada / salida (1M tokens) | $4 / $20 | $5 / $25 |
| Lectura de caché (1M tokens) | $0,20 | $0,50 |
| Terminal-Bench 4.0 | 66,4% | 52,3% |
| Clasificadores de seguridad | Sí, al nivel bio/ciber de Mythos 5.1 | Sí |
fallbacks en la API de Claude |
Sí, en beta | Sí, en beta |
| Limitación / riesgo | Se vende para migraciones de días, y es ahí donde más superficie das a que salte un guardarraíl | Un 20% más caro por token y 14 puntos por debajo en Terminal-Bench |
Fuente: nota de lanzamiento de Opus 5.5, 22 de septiembre de 2026.
Fíjate en la fila de la caché, porque explica el titular: los tokens bajan un 20%, pero las lecturas de caché bajan un 60%. De ahí sale el "40% más barato" que anuncia Anthropic — y solo lo ves entero si buena parte de tu factura eran lecturas de caché, que es justo el caso de los flujos agénticos largos.
Ahora bien: ninguno de esos porcentajes mide lo que va este post, que es cuántas veces se te para el flujo a mitad. Es la diferencia de siempre entre lo que miden los benchmarks de IA programando y lo que te encuentras el viernes por la mañana.
Si vienes de Opus 5, los cambios de API que rompen código son otros y ya los cubrí en su momento: los breaking changes de Opus 5. Lo de aquí se suma a aquello, no lo sustituye.
Qué hacer hoy en tu código: 3 pasos
- Busca dónde lees
response.content[0]. Ese es el punto exacto donde un rechazo se convierte en un bug fantasma. Compruebastop_reason === 'refusal'antes de tocar el contenido, y registrastop_details.categorypara saber después de qué murió. - Trata el rechazo como una rama del flujo, no como un error. Un circuit breaker que abra tras N rechazos seguidos te ahorra los rate limits y la investigación de madrugada. Es la misma idea que el método del ebook gratuito Revisión por Contrato: que un agente no te cuele trabajo a medias sin que nadie se entere.
- Activa
fallbacks: "default"si estás en la API de Claude. Y si estás en Bedrock o Vertex, asume que no lo tienes y monta el fallback en cliente leyendorecommended_model.
Todo esto es la misma idea de fondo: el modelo es un proveedor externo con fallos propios, y tu sistema necesita contratos que aguanten cuando el proveedor dice que no. Si diseñas esa frontera antes de escribir el código —qué entra, qué sale y qué pasa cuando no sale nada— esto deja de ser una sorpresa, y de eso va Spec-Driven Development.
Y si quieres el recorrido completo de construir con estos modelos sin que la primera sorpresa te pille en producción, lo trabajo entero en Construye con IA.
Preguntas frecuentes
¿Un refusal de Claude Opus 5.5 devuelve un error HTTP?
No. Devuelve un HTTP 200 perfectamente válido, con stop_reason: "refusal", el array content vacío y un objeto stop_details con la categoría. Por eso pasa desapercibido: los bloques try/catch y los reintentos basados en códigos de error no lo ven.
¿Me cobran los tokens de una petición rechazada?
Depende de cuándo llegue el rechazo. Si llega antes de cualquier salida, no se factura: content viene vacío y los tokens aparecen en usage pero no se cobran. Eso sí, la petición sí cuenta contra tus rate limits. Si el rechazo llega a mitad de streaming, se facturan los tokens de entrada y la salida ya emitida a precio normal, y esa salida parcial hay que descartarla.
¿Cómo activo el fallback automático a otro modelo?
En la API de Claude, añade fallbacks: "default" a la petición y la cabecera beta server-side-fallback-2026-07-01. La API reintenta la petición en el modelo recomendado para esa categoría de rechazo y te devuelve una sola respuesta; response.model te dice quién contestó. No está disponible en Amazon Bedrock, Google Cloud Vertex AI, Microsoft Foundry ni en la Message Batches API.
¿Puede saltar un guardarraíl haciendo trabajo legítimo?
Sí, y la documentación lo reconoce explícitamente en tres de las cinco categorías: el trabajo benigno de ciberseguridad puede disparar cyber, la investigación útil en ciencias de la vida puede disparar bio y el machine learning normal puede disparar frontier_llm. Si tu organización trabaja en esos dominios, Anthropic tiene programas de verificación para recuperar el acceso completo — el de Life Sciences ya está abierto y el de ciberseguridad lo han anunciado para las próximas semanas.
¿Cuánto cuesta Claude Opus 5.5 frente a Opus 5?
$4 por millón de tokens de entrada y $20 de salida, frente a los $5 / $25 de Opus 5: un 20% menos. El titular del 40% sale de las lecturas de caché, que bajan de $0,50 a $0,20 por millón. Si esa palanca te interesa, tengo un post sobre prompt caching en la API de Claude.
¿Qué hago si stop_details.category viene null?
Trátalo como un caso normal, porque lo es: la documentación avisa de que tanto category como explanation pueden ser null de forma permanente cuando el rechazo no encaja en ninguna categoría con nombre. Tu código debe manejar el rechazo sin depender de conocer el motivo, y nunca parsear el texto de explanation, que no es estable.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
¿Te resultó útil este artículo?
Compártelo con tu comunidad y ayuda a otros desarrolladores.
