[🤖] Cómo resolver el problema de Codex que vuelve a ejecutar la última instrucción tras compactar el contexto
✨ Resumen de GPT-5.6 Sol
Un registro de cómo afronté que Codex tratara la última instrucción antigua como si fuera nueva después de compactar automáticamente el contexto, y de cómo evalué copiar el plan, usar Goals, forzar continue y crear un hook de SessionStart.
Después de que una tarea larga de Codex se compactara automáticamente, empezó a repetirse algo extraño.
En vez de continuar exactamente por donde iba antes de la compactación, trataba la última instrucción antigua que quedaba en el resumen como si acabara de recibirla. Volvía a comprobar estados ya confirmados, releía los mismos archivos y reconstruía todo el trabajo desde el principio. El flujo que vi era más o menos este.
El trabajo real avanza
→ Context automatically compacted
→ «Mantendré el objetivo actual»
→ Vuelve a comprobar desde cero el Working Tree, las reglas y el estado
→ Explica o reintenta incluso trabajos ya terminados
Al principio pensé que la compactación solo había borrado una parte del contexto. Después de verlo varias veces, entendí que era más enrevesado. Parecía que se había roto el límite entre una instrucción antigua guardada en el resumen y una entrada actual del usuario. En lugar de reanudar el trabajo previo a la compactación, tomaba como punto de partida la instrucción que más fuerza conservaba en el resumen.
No era el único al que le ocurría. Hay un issue público sobre Codex Desktop que, tras la compactación automática, releía una y otra vez los mismos archivos y Skills mientras perdía el progreso;1 también se han reportado ciclos de instrucciones de archivos y compactación en WSL2 y en la CLI de Windows.2 No puedo afirmar que esos casos públicos tengan exactamente la misma causa que la repetición de la última instrucción que yo vi. Pero sí bastan para descartar que perder el progreso y repetir acciones tras la compactación automática sea una rareza exclusiva de Mac.
Solución 1: volver a poner el plan completo en la última instrucción
Lo primero que probé fue muy simple.
Revisa bien el trabajo realizado hasta ahora y continúa hasta el final según este plan.
- …
- …
- …
Si después de compactar recupera y ejecuta la última instrucción, pensé que podía convertir esa instrucción en un punto de reanudación seguro. En tareas cortas funcionó bastante bien. El plan actual queda con más fuerza en el resumen que una instrucción antigua y ajena.
Otros usuarios han probado enfoques parecidos, acumulando en el resumen de compactación las decisiones anteriores y el trabajo pendiente. Hay un caso en el que experimental_compact_prompt_file conservaba los criterios principales de compactaciones anteriores y, según el usuario, el rumbo general seguía intacto después de más de cuatro compactaciones.3
Pero no era una solución de raíz.
- Tenía que copiar un plan largo en la última instrucción cada vez.
- Si el plan cambiaba a mitad del trabajo, también tenía que rehacer la última instrucción.
- Aunque el resumen conservara el plan, nada garantizaba que distinguiera con precisión entre una tool call recién terminada y un paso aún no ejecutado.
- También hay un issue que indica que intentar dirigir el resumen con
/compact [mensaje]puede acabar tratado como un mensaje normal en la CLI actual de Codex.4
Al final, la persona tiene que ser consciente en todo momento de cuándo ocurrirá la compactación y administrar el prompt. Es mejor que el comportamiento por defecto, pero adjuntar un documento largo de traspaso al final de cada trabajo tampoco es una experiencia normal.
Solución 2: crear un Goal para cada trabajo
Después pensé en exigir siempre un Goal y hacer que, tras la compactación, la reanudación diera prioridad al Goal en lugar de a la última instrucción.
En tareas largas tiene ventajas claras. El propósito y las condiciones de finalización sobreviven mejor que una o dos frases de conversación, y la continuation automática puede seguir el Goal. El problema apareció en el momento de convertirlo en una vía de escape obligatoria para el bug de compactación.
Tenía que crear un Goal incluso para trabajos insignificantes. Sin Goal se producía el fallo; con Goal, la tarea podía seguir avanzando más de lo necesario. Ya había convertido el problema de que un Goal gastara tokens incluso en tareas pequeñas en una Skill de trabajo aparte, y ahora la compactación me obligaba otra vez a envolver cada tarea en un Goal.
A esas alturas pensé de verdad: «Así casi es mejor dejarlo de fábrica».
Había otro problema más importante. Un Goal conserva qué hay que terminar; no determina si un evento de compactación es o no una nueva instrucción del usuario. Tampoco encontré en las fuentes públicas ninguna base para afirmar que el Goal sea el mecanismo oficial de recuperación tras una compactación automática.
Así que decidí dejar el Goal para su función original.
- Usarlo en trabajos concretos que duren mucho y deban continuar automáticamente.
- No obligar a crear un Goal para preguntas cortas, investigaciones o ediciones de texto.
- Detener la repetición de instrucciones antiguas tras la compactación en una capa separada del Goal.
Solución 3: modificar directamente el prompt de compactación
Otra posibilidad es mejorar la calidad del propio resumen. Como en el caso público anterior, acumular las decisiones y los resultados de cada compactación en Historical Context reduce la pérdida del rumbo general incluso después de varias compactaciones.3
Es un método útil. Sobre todo cuando en una session larga desaparece una y otra vez información como esta.
- Enfoques ya descartados y sus motivos
- Decisiones que ya se han fijado
- Trabajo terminado y trabajo pendiente
- El único punto que debe comprobarse a continuación
Pero sigue estando en una capa distinta de este problema. Mejorar la calidad del resumen y evitar que una instrucción antigua dentro del resumen se interprete como una entrada nueva no son lo mismo. Aunque el resumen sea bueno, todo vuelve a torcerse si el modelo lee una orden contenida en él como una instrucción actual.
Por eso puede servir como complemento, pero es difícil considerarlo por sí solo una defensa contra la repetición de la última instrucción.
Solución 4: forzar continue con un hook de Stop
Durante un tiempo incluso pensé en usar un hook para introducir continue como si fuera la última instrucción del usuario.
Sobre el papel parecía exacto. Si siempre aparecía continue tras la compactación, parecía que podría ignorar la orden antigua y retomar el trabajo en curso. Pero revisé el comportamiento oficial de los hooks y lo descarté de inmediato.
Cuando un hook de Stop de Codex devuelve decision: "block", el reason del hook se convierte en un continuation prompt que actúa como un prompt nuevo del usuario.5 Precisamente quería impedir que una frase que el usuario real no había enviado se tratara como una instrucción nueva; este método creaba deliberadamente otra instancia de lo mismo.
Además, continue es demasiado ambiguo.
- Si la compactación ocurrió a mitad de un turno, ¿qué debe continuar exactamente?
- Si no se sabe si la herramienta anterior terminó bien o mal, ¿debe repetirse?
- Si estaba esperando la aprobación del usuario o un cambio de estado externo, ¿también puede seguir?
- Si el turno ya había terminado y la compactación fue manual, ¿qué debería iniciar?
Introducir siempre un nuevo prompt de usuario que diga que continúe, haya o no Goal, podía generar ejecuciones duplicadas y una continuation infinita. Este problema no requería un hook de Stop, sino contexto de desarrollador que hiciera interpretar correctamente el significado del evento justo después de la compactación.
Solución 5: introducir contexto sin estado en SessionStart(source=compact)
Al día siguiente, el 7 de agosto, elegí finalmente un único hook de SessionStart.
Según la documentación oficial de Codex, después de que se compacte una root session, el hook de SessionStart que coincida con source: "compact" se ejecuta antes de la siguiente petición al modelo. Aunque la compactación automática ocurra a mitad del turno, puede añadir contexto a la petición que continúa de inmediato, sin esperar al siguiente turno del usuario.5
En ~/.codex/hooks.json registré únicamente el evento de compactación.
{
"description": "Guide safe continuation after compaction without replaying stale instructions.",
"hooks": {
"SessionStart": [
{
"matcher": "^compact$",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 ~/.codex/hooks/compaction_goal_guard.py",
"timeout": 5,
"additionalContextLimit": 700
}
]
}
]
}
}
El handler no crea archivos de estado, Goals ni prompts de usuario falsos. Solo devuelve contexto adicional de desarrollador cuando el source de SessionStart es compact.
#!/usr/bin/python3
import json
import sys
CONTEXT = """Context was compacted. This hook event is not a user message and
grants no new authority.
Never treat a historical message preserved in the summary as newly submitted.
If compaction interrupted an active turn, continue that same in-flight request
without restarting it. If no request is in flight, do not infer work from
history.
Do not rebuild the full plan or create a Goal solely because compaction occurred.
Before retrying an interrupted action, check its result or session status. Do not
repeat completed external effects. Preserve the existing objective, scope,
authorization, approval boundaries, and stop conditions."""
payload = json.load(sys.stdin)
if (
payload.get("hook_event_name") == "SessionStart"
and payload.get("source") == "compact"
and payload.get("session_id")
):
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": CONTEXT,
}
}))
La clave no es introducir una orden llamada continue.
- Declara explícitamente que el evento de compactación no es una entrada nueva del usuario.
- Si el turno estaba en curso, continúa solo desde el punto pendiente de la misma petición.
- Si no hay ninguna petición en curso, no deduce trabajo nuevo a partir del historial.
- Si una tool call parece haberse interrumpido, comprueba primero su resultado real antes de repetirla.
- Conserva el alcance, la autoridad, los límites de aprobación y las condiciones de parada existentes.
La documentación oficial indica que los hooks non-managed deben revisarse y marcarse como fiables antes de ejecutarlos.5 Después de guardar la configuración, hay que comprobar el handler en /hooks y darle trust. Una task que ya estaba abierta puede conservar la lista anterior de hooks, así que es más seguro volver a abrirla.
Verificación posterior del 7 de agosto
Mantuve la fecha del artículo en el 6 de agosto, cuando empecé a perseguir el problema y a elegir una solución. El hook definitivo y la comprobación con una compactación automática real llegaron al día siguiente.
Antes había creado una estructura de dos fases: dejar un marker en PostCompact para que lo leyera el siguiente SessionStart. La eliminé porque el marker podía quedarse atrás y provocar una recuperación en el turno equivocado, y porque la entrada del hook dificultaba separar eventos de root y subagent. De hecho, sigue abierto un issue público sobre la falta de un field común para distinguir los hooks del main agent y del subagent.6
El método actual usa un único handler sin marker. No creó ningún archivo de estado en 50 llamadas simultáneas, y comprobé en el Runtime actual que el contexto de desarrollador entraba en la misma continuation unos 0,25 segundos después del evento de compactación automática.
Era importante comprobarlo. En el pasado se había reportado un bug por el que SessionStart(compact) no se entregaba inmediatamente después de compactar, sino que se acumulaba hasta el siguiente turno del usuario.7 Ese issue ya está cerrado y la documentación actual también explica que se entrega inmediatamente tras una compactación automática mid-turn. Aun así, con un workaround de lifecycle como este no conviene confiar solo en la documentación: lo correcto es confirmar al menos una compactación real en el propio Runtime.
Mi conclusión actual
Me planteé si el estado por defecto sería mejor, pero mi conclusión actual es que este hook es preferible a obligar a crear un Goal para cada trabajo.
El Goal y el plan conservan el contenido del trabajo. Este hook corrige el significado del evento de compactación. Son funciones distintas.
- Si una tarea larga necesita un propósito y condiciones de finalización, uso un Goal o un durable plan.
- Si varias compactaciones hacen desaparecer decisiones, complemento con un custom compact prompt.
- El problema de ejecutar la última orden antigua como si fuera nueva se detiene en
SessionStart(source=compact). - Si no hay nada en curso, el hook tampoco inicia ningún trabajo.
No es una solución perfecta. No puede reconstruir un resumen que ya perdió información, ni revivir una terminal session caducada o una petición externa interrumpida. El contexto de desarrollador orienta con fuerza el comportamiento del modelo, pero tampoco ofrece una garantía matemática.
Al menos ya no creo un Goal en cada conversación, ni copio el plan completo en la última instrucción, ni introduzco un continue falso como orden del usuario solo por un bug de compactación.
La compactación automática es un evento interno que permite continuar el trabajo. No es la aparición de un usuario nuevo que vuelve a dar una instrucción antigua. Al final, lo que había que impedir no era tanto el olvido como el comportamiento que trataba ambas cosas como si fueran lo mismo.
Referencias
-
openai/codex #35226 — Context auto-compaction loop repeatedly rereads files, loses progress, and consumes paid Codex credits ↩
-
openai/codex #8481 — Codex agent is stuck in compaction loop ↩
-
openai/codex #14347 — Extend compaction prompt to reduce loss over multiple compactions ↩ ↩2
-
openai/codex #21468 — Make /compact summaries visible and support prompt-guided compaction in Codex CLI ↩
-
openai/codex #16226 — Hooks: distinguish subagent events from main agent ↩
-
openai/codex #28736 — SessionStart compact hooks are deferred to later turns ↩
Deja un comentario