Montas un guardia de seguridad para tu pipeline de IA. La orden es clara: si un modelo barato genera el contenido, páralo antes de que se publique. Y el guardia cumple. Varias veces a lo largo de dos semanas, tira a la basura trabajo perfecto de tu modelo caro. ¿El motivo? Claude Code usa Haiku internamente para ponerle nombre a la sesión, y tu guardia no sabe distinguir esa llamada interna del trabajo real.
El asesino llamando a la policía. Bienvenido a operar LLMs en producción.
TL;DR: El resumen sin rodeos
- Haiku fantasma: Claude Code ejecuta llamadas internas a Haiku (nombrar sesión, routing) que aparecen en
.modelUsagejunto a tu trabajo real. Tu guard las confunde con el modelo que hizo el curro. - Tres heurísticas, tres fracasos: primer modelo alfabético, el de más tokens y match exacto de ID fallan por la misma causa: mezcla de modelos internos con trabajo real.
- El flag
--modelno funciona: enclaude -pheadless solo vale la envANTHROPIC_MODEL. Sin fijarla, el default puede estar obsoleto o degradar a Haiku en silencio. - Fix robusto: excluir Haiku de
.modelUsage, coger el de más tokens del resto. Solo si todo es Haiku, degradación real.
¿Qué hace Claude Code con Haiku cuando tú no miras?
Claude Code en modo headless (claude -p) mezcla modelos internos con el modelo solicitado en la misma sesión: ejecuta llamadas a Haiku para tareas auxiliares (nombrar la sesión, routing) que tú ni pediste ni necesitas. Esos turnos aparecen en el campo .modelUsage y en el transcript JUNTO a tu trabajo real, sin nada que los distinga.
¿Y sabéis qué significa eso? Que cualquier lógica que mire qué modelo respondió sin filtrar esas llamadas internas se va a equivocar. No a veces. SIEMPRE.
Hay más. En claude -p, el flag --model se ignora. Solo funciona la variable de entorno ANTHROPIC_MODEL. Si no la fijas explícitamente, el CLI coge un default que puede estar obsoleto. Y si tu plan Max está cerca del límite de uso, Claude Code degrada a Haiku en silencio. Sin warning. Sin error. is_error: false.
En Marketing Ultra descubrimos esto el 14 de junio depurando nuestro pipeline de contenido con IA. Lo que vino después fue peor.
Tres heurísticas rotas, tres formas de morder

Probamos tres métodos para detectar qué modelo había respondido de verdad. Los tres nos mordieron.
keys[0]: primer modelo en orden alfabético. "Haiku" va antes que "Opus" en el abecedario. Nuestro heartbeat usaba este método. Resultado: reportó Haiku 24/7 durante ~14 días. Todo el histórico de modelo en los logs, contaminado. Catorce días de datos falsos que tardamos en detectar porque el pipeline funcionaba bien. Solo el registro era mentira.
sort_by(-outputTokens)[0]: el modelo con más tokens de salida. Parece el método inteligente, ¿verdad? El que más genera es el que hizo el curro. Hasta que Opus te devuelve una respuesta corta y el título generado por Haiku tiene más tokens que la respuesta real. El log dice Haiku. El trabajo es Opus.
Exact key match contra un ID conocido. Los IDs de modelo llevan fecha embebida: claude-opus-4-6-20250401. Nuestro guard comparaba contra claude-opus-4-6 a secas. No coincidía. Exit 75: "modelo degradado". Trabajo perfecto de Opus desechado. Varias veces.
Tres heurísticas "obvias". Tres formas de morder. Todas por el mismo fallo de fondo: asumir que .modelUsage refleja solo TU trabajo.
El misdiagnóstico que nos costó 697 segundos de Opus
19 de junio. El guard va por la versión 3. Lee el transcript directamente. Problema resuelto.
¿Seguro?
El guard cogía el ÚLTIMO turno de assistant con tail -1. Y el último turno era el título que genera Haiku. El SEO Strategist acababa de producir una estrategia completa con Opus. 697 segundos de cómputo. El guard mira el transcript, ve Haiku al final, marca model_downgraded.
Once minutos de modelo caro. A la basura.
Pero la parte que me jode de verdad es lo que vino después. Miro el log de errores buscando patrón. Encuentro entradas viejas con model_downgraded y un hint: "Max usage limit". Leo el texto del error. Asumo que es la cuota del plan Max cerca del tope. Tiene sentido, ¿no?
No. Dani lo cazó revisando la cronología: las fechas no cuadraban. Esas entradas no eran degradaciones reales. Eran falsos positivos de heurísticas anteriores que habían dejado basura en el log. El hint "Max usage limit" estaba hardcodeado como mensaje por defecto cuando el guard no podía determinar la causa.
Yo leí el texto del error en vez de verificar el transcript original. Y eso me llevó a "arreglar" algo que no estaba roto, ignorando lo que sí lo estaba.
Y esto aplica a TODO, no solo a IA: un log que dice "cuota agotada" no significa que la cuota esté agotada. Significa que algo escribió "cuota agotada" en el log. Verifica el dato de origen, no el mensaje derivado. SIEMPRE.
¿Cómo saber qué modelo trabajó de verdad en Claude Code?

El fix robusto (v5) es simple una vez pillas la mecánica:
- Lee
.modelUsagedel transcript. - Excluye TODOS los modelos Haiku. Son llamadas internas, housekeeping.
- Del resto, coge el que tenga más
outputTokens. Ese hizo tu trabajo. - Solo si TODOS los modelos son Haiku: degradación real. Márcalo.
Añadimos un test permanente que simula un transcript con Haiku + Opus y verifica que el guard reporta Opus. Si alguien toca la lógica y la rompe, el test lo caza antes de producción. Cero confianza, verificación automática.
Esto aplica a cualquiera que opere agentes de IA en pipelines reales: para saber qué modelo trabajó, nunca cojas el primero, ni el último, ni el de más tokens a secas. Excluye las llamadas internas y mira solo el trabajo real.
Si operas LLMs en producción, no basta con pedir Opus. Tienes que verificar que Opus respondió.
La próxima vez que tu guard salte y el log te cuente una historia, abre el transcript. Lee lo que pasó de verdad. Esto no es un edge case. Esto es operar IA en serio.
Preguntas frecuentes sobre Claude Code headless y detección de modelo
¿Qué es el modo headless de Claude Code (claude -p)?
Es el modo de Claude Code sin interfaz interactiva, pensado para pipelines y automatización. Le pasas un prompt por stdin o argumento, ejecuta y te devuelve el resultado por stdout en JSON. El flag --model no funciona en este modo: el modelo se controla exclusivamente con la variable de entorno ANTHROPIC_MODEL.
¿Qué es .modelUsage en el transcript de Claude Code?
Es un campo del JSON de salida que lista todos los modelos invocados durante la sesión con sus tokens consumidos. Incluye tanto el modelo que hizo tu trabajo como los que se usan internamente (Haiku para nombrar la sesión), así que no sirve para saber qué modelo trabajó de verdad si no filtras las llamadas internas.

