El 12 de octubre de 2026 un issue de GitHub llegó a la portada de Hacker News y alcanzó 437 puntos y 229 comentarios en menos de un día. No es un cuelgue, ni una vulnerabilidad, ni una función rota. El título es una petición: «CLI: make daemon_auto_start opt-in — a persistent, self-updating background daemon shouldn’t be the default».
El issue #48195 se abrió el 25 de septiembre de 2026 a las 20:04:30 UTC. El paquete npm del que se queja, @openai/codex@0.157.0, se había publicado ese mismo día a las 02:35:19 UTC: 17 horas y 29 minutos antes. En ese intervalo, el CLI de Codex había adquirido un nuevo comportamiento por defecto: abrir una sesión interactiva ahora arranca un servidor en segundo plano desacoplado, copia unos 373 MB de binarios de plataforma a un directorio que no gestiona tu gestor de paquetes y lanza un bucle que reinstala Codex desde la red cada cierto tiempo.
El issue acumula 129 reacciones, 115 de ellas 👍. Sigue abierto. Y el valor por defecto de la función también.
Este análisis no se apoya en el hilo, sino en el código: el commit de fusión, los metadatos del registro npm de todas las versiones estables desde septiembre, el crate de Rust que implementa el daemon, el script de instalación que ejecuta la instalación y las tres páginas de configuración que OpenAI publica. Casi todas las cifras que siguen las medí yo mismo, y los hallazgos más interesantes no están en el hilo: están en archivos que nadie enlazó.
El cambio fueron diez líneas, fusionadas por un bot, tres días antes del lanzamiento
El daemon no llegó con un anuncio de producto. Llegó con el pull request #47179, titulado «Enable automatic daemon startup by default», fusionado el 22 de septiembre de 2026 a las 03:20:41 UTC. Su diff es de +10 / −50 líneas en 6 archivos. Lo fusionó copyberry[bot]. El cuerpo, completo salvo una huella de procedencia del bot, dice:
Enable automatic daemon startup by default
What changed
Promote
daemon_auto_startto stable and enable it by default for eligible interactive launches. Remove its entry from/experimental.Testing
Update daemon startup,
--no-daemon, and startup failure tests to exercise the default without explicitly enabling the feature.
Un PR complementario, #47318 («Offer explicit recovery for incompatible background servers», +698 / −47 en 18 archivos), añadió los diálogos de recuperación. Su justificación es la declaración más honesta del compromiso que implica el cambio:
Automatic daemon launches could silently fall back to embedded mode when shared feature settings differed from the session’s requirements. Changing those settings affects other clients, and restarting the server may interrupt active or queued work.
Es decir: la función salió de la fase experimental el 22 de septiembre, se publicó en la 0.157.0 el 25 de septiembre y la nota de versión le dedicó una sola frase:
Enabled automatic background-server startup for eligible interactive sessions, with recovery choices when server settings are incompatible. (#47179, #47318)
Ese es todo el aviso público. No dice persistente, ni en segundo plano, ni desacoplado, ni con autoactualización, ni copia 373 MB, ni sobrevive al cierre de la ventana. Quien lea la nota se enterará de que arranca un servidor; no de que sobrevive a la sesión que lo lanzó.
La función sigue activada por defecto, y puedo enseñar la línea exacta
Codex es código abierto (Apache-2.0, 128.839 estrellas, 654 watchers, 20.237 forks). Sus banderas de funciones viven en codex-rs/features/src/lib.rs, y la que nos ocupa es una entrada de dos líneas:
FeatureSpec {
id: Feature::DaemonAutoStart,
key: "daemon_auto_start",
stage: Stage::Stable,
default_enabled: true,
},
Descargué ese archivo en tres etiquetas git distintas y en main:
| Referencia | daemon_auto_start | Fase | default_enabled | Publicada |
|---|---|---|---|---|
rust-v0.157.0 | presente | Stable | true | 2026-09-25 |
rust-v0.160.0 | presente | Stable | true | 2026-10-01 |
rust-v0.162.1 | presente | Stable | true | 2026-10-09 |
main | presente | Stable | true | — |
Seis versiones estables y diecisiete días después del issue, en un periodo en el que se publicaron 24 versiones estables, el valor por defecto no se ha movido. El comentario del enum que lo acompaña dice: «Automatically start the shared local daemon for eligible interactive launches». Ahí tampoco se menciona la persistencia: esa propiedad vive en el crate del daemon, no en la bandera.
Qué hace realmente el daemon en el disco
Esta es la parte que ningún resumen del hilo de HN acierta, porque la mecánica está en un crate que casi nadie abre: codex-rs/app-server-daemon. Son 37 blobs, 417.040 bytes, 34 archivos Rust, de los cuales 11 son pruebas (135.642 bytes). Los archivos más grandes ya delatan el alcance: lib.rs (53.399 bytes), update_loop.rs (30.022 bytes, 799 líneas), pid.rs (28.342 bytes, 809 líneas), prepare_install.rs (19.485 bytes), más managed_install.rs, manual_update.rs, migration.rs, install_lock.rs, thread_recovery.rs y telemetry.rs. En todo el árbol del repositorio hay 101 rutas que contienen la palabra «daemon».
Dos directorios son los que importan:
| Ruta | Función | Evidencia en el código |
|---|---|---|
~/.codex/app-server-daemon/ | Estado: settings.json, daemon.lock, daemon-updater.pid | STATE_DIR_NAME = "app-server-daemon", SETTINGS_FILE_NAME = "settings.json" |
~/.codex/packages/app-server-daemon/ | Instalación gestionada: releases/<version>/ y un enlace simbólico current | package_root() devuelve codex_home/packages/app-server-daemon (heredado: packages/standalone) |
La copia no es un enlace ni una referencia a lo que instaló tu gestor de paquetes. prepare_install.rs documenta así la función que la construye:
Hash the complete tree and optionally copy those same bytes. Relative file links are materialized; escaping links and directory links are rejected.
El proceso prepara el contenido en un directorio temporal .staging.* dentro de releases/, valida un manifiesto codex-package.json, calcula una ExecutableIdentity con blake3 (un resumen más un path_digest cuyo comentario dice «Distinguish release generations whose executable bytes are identical») y después se niega a tocar un directorio existente que difiera:
an existing daemon release has different contents; refusing to overwrite it
Esa última salvaguarda es ingeniería cuidadosa, y también la razón de que cada versión se convierta en un directorio nuevo permanente en lugar de sustituir nada. Eso importa enseguida.
373 MB por versión, y el contador que no deja de subir
El registro npm publica el tamaño descomprimido de cada versión, así que el coste de este diseño se puede medir en lugar de discutir. El paquete @openai/codex que instalas directamente es un envoltorio de 13.206 bytes, 3 archivos y cero dependencias; los binarios llegan por seis optionalDependencies que son alias del mismo nombre de paquete con sufijo de plataforma (npm:@openai/codex@0.157.0-linux-x64). Un artefacto por plataforma:
| Versión | linux-x64 | win32-x64 | darwin-arm64 | Publicada (UTC) |
|---|---|---|---|---|
| 0.156.0 | 368,9 MB | — | — | 2026-09-22 |
| 0.157.0 | 373,0 MB | 425,5 MB | 313,7 MB | 2026-09-25 |
| 0.158.0 | 423,6 MB | — | — | 2026-09-28 |
| 0.160.0 | 426,0 MB | 429,7 MB | 317,5 MB | 2026-10-01 |
| 0.161.0 | 429,5 MB | 434,9 MB | 321,0 MB | 2026-10-07 |
| 0.162.0 | 431,5 MB | 437,3 MB | 322,4 MB | 2026-10-08 |
| 0.162.1 | 431,5 MB | 436,1 MB | 322,5 MB | 2026-10-09 |
El artefacto de Linux creció 58,5 MB (un 15,7 %) en catorce días. Todos llevan exactamente 46 archivos, así que no es un montón de complementos: es un binario estático enorme más un ayudante de sandbox. Eso es lo que quería decir un comentarista con «each codex bin is somehow ~300MB».
Y ahora la parte que convierte un tamaño en una factura. Sumemos los artefactos de Linux de las doce versiones estables publicadas entre la 0.157.0 y la 0.162.1:
373,0 + 373,0 + 423,6 + 423,7 + 423,8 + 423,8 + 424,1 + 426,0 + 426,1 + 429,5 + 431,5 + 431,5 = 5.009,6 MB ≈ 4,89 GB
Ese es el máximo teórico acumulado solo por la copia gestionada en catorce días, con una media de 417,5 MB por versión. Lo de «teórico» es justo la pregunta que la documentación no responde, así que la respondí leyendo el código.
En todo este diseño nada borra jamás una versión antigua
Este es el hallazgo que me habría perdido si solo hubiera leído el issue y el hilo, porque en el hilo todo el mundo describe síntomas y nadie demuestra la ausencia.
Descargué los 34 archivos Rust del crate del daemon (404.356 bytes) y busqué cualquier política de retención: prune, retain_only, old_release, max_releases, remove_dir_all.
- Cero coincidencias para
prune,retain_only,old_releaseymax_releases. - Las únicas llamadas a
remove_dir_allestán eninstall_lock.rs, borrando directorios de bloqueo obsoletos cuyo propietario murió hace más de 600 segundos, y en la limpieza del propio bloqueo del actualizador.
Después comprobé el otro sitio donde podría vivir esa limpieza. El script de instalación scripts/install/install.sh (34.564 bytes, 1.305 líneas) tiene una función llamada cleanup_stale_install_artifacts(). Su cuerpo entero:
find "$RELEASES_DIR" -mindepth 1 -maxdepth 1 -name '.staging.*' -exec rm -rf {} +
find "$STANDALONE_ROOT" -mindepth 1 -maxdepth 1 -name '.current.*' -exec rm -f {} +
Elimina directorios de preparación interrumpidos y enlaces temporales abandonados. No hay barrido por -mtime, ni «conserva las últimas N», ni borrado de un releases/<version>/ sustituido. El equivalente de Windows (scripts/install/install.ps1, 44.363 bytes, 1.208 líneas) sigue el mismo diseño, y lo único que hace con lo «antiguo» es pedir confirmación antes de reemplazar una instalación visible anterior.
Conclusión: el daemon no sobrescribe una versión con bytes distintos, y ni el daemon ni el instalador borran una versión que ya no es current. Los números de la comunidad encajan con ese diseño, y son grandes: un usuario reporta 11 binarios de unos 300 MB cada uno en ~/.bun/install/cache/@openai; otro, que Codex le ocupa 12 GB en total. El mantenedor de OpenAI charliermarsh respondió en ese hilo: «I haven’t worked with the update subsystem before but that seems like an oversight to me. I’m gonna fix it». En la última versión que medí (0.162.1, 2026-10-09) el código todavía no contiene lógica de retención.
El actualizador descarga un script de la red y se lo pasa a /bin/sh
update_loop.rs es donde vive la autoactualización, y sus constantes son exactamente lo que afirmaba el issue:
| Constante / comportamiento | Valor | Origen en el código |
|---|---|---|
| Primera comprobación tras arrancar | 5 minutos | INITIAL_UPDATE_DELAY = Duration::from_secs(5 * 60) |
| Intervalo posterior | 60 minutos | DEFAULT_UPDATE_INTERVAL_MINUTES: u32 = 60 |
| Intervalo si está desactivado | el bucle termina (None) | next_update_delay() devuelve None si !auto_update_enabled |
| Origen del instalador (unix) | https://chatgpt.com/codex/install.sh | INSTALL_URL |
| Origen del instalador (Windows) | https://chatgpt.com/codex/install.ps1 | INSTALL_URL |
| Cómo lo ejecuta | /bin/sh -s, process_group(0) | Command::new("/bin/sh").arg("-s") |
Es un proceso desacoplado, en su propio grupo de procesos, que entrega a un shell un script descargado por HTTPS, de forma periódica, sin TTY y sin ninguna notificación en pantalla. El script que descarga es el mismo install.sh de 33,8 KB publicado como artefacto de la versión (en Windows, install.ps1, 42,8 KB). El actualizador pasa variables de guarda para que el instalador sepa que lo lanzó un actualizador y no una persona — CODEX_INSTALL_IF_LATEST, CODEX_INSTALL_IF_CURRENT, CODEX_INSTALL_DAEMON_ONLY, CODEX_UPDATE_FROM_RELEASE — y el script lee el registro de PID del actualizador (daemon-updater.pid) para verificar la paternidad antes de continuar.
Aquí son ciertas dos cosas a la vez. La ingeniería es defensiva: HTTPS, comprobaciones de identidad con blake3, un bloqueo de instalación real compartido con el instalador del CLI, negativa a sobrescribir una versión con bytes distintos y una ruta de restauración a producción. Y el modelo operativo sigue siendo un proceso en segundo plano que reejecuta cada hora un script de instalación remoto, fuera de tu gestor de paquetes, desde una copia de sí mismo. Si eres administrador de sistemas, empaquetador de Nix o responsable de parques corporativos, esa frase es toda la revisión.
El interruptor existe. El techo son 300 segundos.
El issue #40969, abierto el 26 de agosto de 2026 y aún abierto, es el que predice daño real y no solo molestia. Su título: «app-server daemon: auto-update force-kills active turns after a 60s drain budget, and cannot be disabled». El autor ejecutaba 13 hilos de larga vida en los que «typical turn runs a Go build/test suite lasting 3–30 minutes», y documentó un episodio sin nadie delante:
20:16— el actualizador instaló la 0.150.0 y reapuntó el enlacecurrent.20:16 → 20:17:51— el app-server se reinició; todas las conexiones de cliente cayeron a la vez; los turnos en ejecución se terminaron. El hueco de unos 90 segundos coincide con el periodo de gracia antes del cierre forzado.
El daemon sí implementa un drenaje ordenado correcto, y los mantenedores hicieron configurable el presupuesto. Pero yo leí la implementación actual, no la propuesta del issue, y la diferencia importa:
pub(crate) const DEFAULT_SHUTDOWN_GRACE_SECONDS: u32 = 60;
pub(crate) const MAX_SHUTDOWN_GRACE_SECONDS: u32 = 5 * 60;
validate_shutdown_grace() exige seconds <= 300, con el texto de error «shutdown grace must be between 0 and 300 seconds». Y en pid.rs, la ruta de parada es:
let force_after = Duration::from_secs(grace_seconds.into());
let deadline = started_at + force_after + STOP_FORCE_TIMEOUT; // STOP_FORCE_TIMEOUT = 10s
Leyendo esas tres líneas juntas queda claro cuál es el arreglo y cuál es su límite:
- El ajuste es real:
shutdown_grace_secondsse puede fijar en elsettings.jsondel daemon. 0no significa «esperar para siempre». Significa cierre forzado a los diez segundos. Un fork de la comunidad propusostopGracePeriodSecs: 0como ilimitado, sin SIGKILL automático durante el drenaje; eso no es lo que se implementó.- El máximo son 300 segundos. La carga del denunciante son turnos de 3 a 30 minutos. En el techo, una compilación normal sigue superando el presupuesto y el drenaje sigue perdiendo trabajo.
Además, los ajustes que gobiernan esto no están donde los buscaría alguien que usa Codex. Viven en un archivo gestionado por el daemon con claves en camelCase — remoteControlEnabled, featureOverrides, shutdownGraceSeconds y un objeto anidado updater con autoUpdateEnabled y updateIntervalMinutes — mientras que la bandera que casi todo el mundo acabará tocando vive en config.toml como bandera de función:
[features]
daemon_auto_start = false
o, de forma equivalente, codex features disable daemon_auto_start. Dos salidas distintas, en dos formatos distintos, en dos archivos distintos, y solo una de ellas documentada.
El hueco documental es medible, y es total
No quería afirmar «no está documentado» basándome en un comentario de foro, así que descargué las tres páginas de configuración que OpenAI publica y conté la palabra.
| Página | Texto extraído | Apariciones de «daemon» | Apariciones de check_for_update_on_startup |
|---|---|---|---|
/codex/config-reference | 226.200 caracteres | 0 | 4 |
/codex/config-basic | 40.150 caracteres | 0 | 0 |
/codex/config-advanced | 75.984 caracteres | 0 | 0 |
La referencia completa de configuración —226.200 caracteres de texto, 1,4 MB de HTML— documenta check_for_update_on_startup con la indicación «Check for Codex updates on startup (set to false only when updates are centrally managed)» y no usa ni una sola vez la palabra daemon. Ni daemon_auto_start, ni shutdownGraceSeconds, ni autoUpdateEnabled, ni «background server». Lo mismo ocurre en las páginas básica y avanzada.
El docs/config.md del repositorio es ahora un stub de 726 bytes que no contiene más que tres enlaces a esas páginas. Así que no hay un tercer sitio donde pueda esconderse la información: el cambio de comportamiento más grande del trimestre en el CLI está ausente del 100 % de la documentación de configuración publicada por OpenAI, mientras un ajuste mucho menor relacionado con actualizaciones aparece documentado cuatro veces en la misma página.
El conflicto con el empaquetado no es un informe de error, es una colisión de diseño
El issue #48050 es la exposición más limpia del problema estructural. En Arch Linux, actualizar el paquete openai-codex de 0.156.1-1 a 0.157.0-1 dejó el CLI inservible:
Error: this CLI has no complete local package; install a packaged Codex CLI or use the standalone installer
To work without the background server, rerun the same command with --no-daemon
El autor comprobó lo evidente: un CODEX_HOME nuevo, y pacman -Qkk openai-codex informando «15 total files, 0 altered files». El paquete estaba intacto. El CLI se negaba a ejecutarse porque quería un árbol de paquete gestionado —la copia en ~/.codex/packages/...— que un paquete de distribución no tiene ninguna obligación de proporcionar. La confirmación llegó desde otros empaquetadores: NixOS/nixpkgs #567098 («codex: 0.156.1 fails to start», 26 de septiembre) y un comentario del port de OpenBSD que «lo arregló» parcheando el código fuente:
- default_enabled: true,
+ default_enabled: false,
Los empaquetadores de Nix van más lejos y eliminan por completo el daemon y el autoactualizador, porque un binario que se actualiza a sí mismo dentro de un almacén inmutable no es una cuestión de preferencias. Relacionado y todavía abierto: #46468, donde una actualización por npm dejó el app-server en 0.154.0 mientras el CLI ya era 0.155.0, «with no supported recovery path»: la segunda vez que ese usuario se topa con lo mismo.
En honor a la equidad: no es un patrón exclusivo de OpenAI. Claude Code conserva binarios anteriores en ~/.local/share/claude/versions/ y ejecuta tareas en segundo plano, y su propia petición de limpieza de versiones (anthropics/claude-code#14980) se cerró. Quienes señalan ese paralelismo tienen razón; solo significa que la práctica se extiende, no que sea inocua.
La contraargumentación es real y explica por qué esto se publicó
Busqué razones para dar la razón a los mantenedores y encontré varias.
El daemon es la base del control remoto, no un proceso decorativo. El PR #47318 resolvía un fallo auténtico: cuando los ajustes compartidos de funciones diferían, los arranques automáticos podían caer silenciosamente al modo integrado, sin que el usuario entendiera por qué su sesión se comportaba distinto a la de un compañero. El comportamiento implementado —ofrecer opciones explícitas, cancelar por defecto y exigir confirmación antes de reiniciar un servidor con clientes conectados— es una resolución defendible de un problema difícil con múltiples clientes. Otras dos funciones de esa versión (/import en sesiones remotas y de daemon, #47317) no existen sin un servidor de larga vida.
Terceros construyen sobre él. Un comentarista que defiende que la bandera debería estar desactivada por defecto también afirma que el daemon «is useful when building apps on top of the daemon server, e.g. pi or pi-durable custom harnesses». Es el mejor argumento a favor en todo el hilo, y viene de un crítico.
En ejecuciones repetidas es mediblemente más rápido. Un desarrollador que estaba midiendo su propia herramienta se dio cuenta de que Codex iba «surprisingly fast after the initial run» y empezó a envolver los harnesses en bwrap para obtener cifras comparables. Arrancar en frío un proceso de este tamaño es genuinamente caro; amortizarlo es justamente el objetivo del diseño.
«Los navegadores también lo hacen» es un argumento, y tiene réplica. El comentario más votado defiende la actualización automática como norma moderna. La respuesta que caló más hondo: los gestores de paquetes ya actualizan miles de programas sin que cada aplicación se instale a sí misma a su manera.
Los mantenedores responden con claridad… a todo menos al valor por defecto. Entre la 0.158.0 y la 0.162.1 encontré 15 entradas de changelog relacionadas con el daemon, todas de ingenieros de OpenAI: omitir el arranque automático en homes de WSL montados desde Windows, distinguir la identidad de una versión cuando los bytes del ejecutable coinciden, recuperar el arranque tras borrar el directorio de trabajo, conservar los registros del actualizador en los diagnósticos, corregir fallos de junctions y bloqueos de archivos en Windows, y el arreglo de la 0.162.1 para fallos de arranque por desajustes de configuración de funciones (#52648). Diecisiete días de endurecimiento. Cero reconsideración de default_enabled: true.
Conviene decirlo con precisión: no es abandono. Es un orden de prioridades en el que endurecer el nuevo comportamiento por defecto pesa más que preguntarse si debería ser el comportamiento por defecto.
Qué hacer si usas el CLI de Codex, o si distribuyes algo parecido
Si solo quieres recuperar tu máquina, en el orden que yo seguiría: desactiva la bandera (codex features disable daemon_auto_start, o [features] daemon_auto_start = false en ~/.codex/config.toml); detén lo que esté corriendo (codex app-server daemon stop y después pkill -f 'app-server daemon pid-update-loop', porque varios usuarios reportan que el actualizador sobrevive al comando documentado); verifica que no queda nada; y recupera disco borrando ~/.codex/packages/app-server-daemon y, si instalaste por npm o bun, las copias antiguas de tu caché de paquetes. Ojo con las dos rutas del código: el directorio de estado del daemon y la raíz del paquete gestionado son distintos, y borrar solo el primero no elimina varios GB de binarios.
Si empaquetas para una distribución, asume que el CLI puede exigir un árbol de paquete gestionado y que --no-daemon es una salida obligatoria en tu envoltorio mientras aguas arriba no cambie el requisito. El error de Arch citado arriba es el modo de fallo que hay que probar: la comprobación no es «¿está instalado Codex?», sino «¿existe una instalación gestionada?», y donde se rompe es con un CODEX_HOME limpio.
Si configuras el presupuesto de gracia, recuerda que el techo son 300 segundos. Si tus turnos automatizados suelen durar más de cinco minutos, hoy no existe configuración soportada que impida que una actualización automática los mate; la única versión fiable de esa garantía es updater.autoUpdateEnabled: false en el settings.json del daemon, junto con desactivar el arranque automático para que un daemon nuevo no herede los valores por defecto.
Si diseñas un CLI con componente en segundo plano, la lección reutilizable no es «los daemons son malos». Es que tres cosas deben salir juntas: el valor por defecto, la documentación y la política de limpieza. Esta versión publicó la primera, media segunda y ninguna tercera, y el hilo resultante es lo que pasa cuando los usuarios tienen que deducir el ciclo de vida de los procesos de su propio software leyendo un archivo de Rust de 30 KB.
Preguntas frecuentes
¿Qué cambió exactamente en el CLI de Codex 0.157.0? daemon_auto_start pasó de experimental a Stage::Stable con default_enabled: true, así que un arranque interactivo elegible inicia un app-server compartido en segundo plano sin preguntar. La consecuencia para el usuario: un proceso persistente que sobrevive a todas las ventanas de Codex, ejecutándose desde una copia del binario en ~/.codex/packages/, más un bucle de actualización que comprueba a los cinco minutos del arranque y luego cada hora.
¿Sigue siendo el valor por defecto hoy? Sí. Comprobé codex-rs/features/src/lib.rs en rust-v0.157.0, rust-v0.160.0, rust-v0.162.1 y main: las cuatro referencias dicen default_enabled: true. El issue #48195 sigue abierto con 129 reacciones, diecisiete días y seis versiones estables después de presentarse.
¿Cómo lo desactivo? codex features disable daemon_auto_start, o [features] daemon_auto_start = false en ~/.codex/config.toml, o --no-daemon en cada invocación. Para detener también las actualizaciones automáticas, pon autoUpdateEnabled: false dentro del objeto updater de ~/.codex/app-server-daemon/settings.json, un archivo que la referencia oficial de configuración no menciona.
¿Cuánto disco ocupa? La copia gestionada es un duplicado byte a byte de un artefacto de Linux que va de 373,0 MB (0.157.0) a 431,5 MB (0.162.1), así que una máquina con la instalación de npm y la copia gestionada alberga dos. Las doce versiones estables entre la 0.157.0 y la 0.162.1 suman 4,89 GB si se retienen. Ni el crate del daemon ni install.sh purgan versiones antiguas, y los usuarios reportan 11 binarios apilados de unos 300 MB y totales de 8 a 12 GB.
¿Puede una actualización automática matar mi tarea en ejecución? Sí, si el turno supera el presupuesto de gracia. La gracia por defecto son 60 segundos, el máximo son 300 (MAX_SHUTDOWN_GRACE_SECONDS) y el plazo forzado es gracia + 10 segundos. Un valor de 0 significa cierre forzado casi inmediato, no «esperar indefinidamente»: el autor del issue #40969 documentó una ventana de 90 segundos en la que cayeron todos los clientes conectados y se terminaron los turnos en ejecución.
¿Por qué la documentación oficial no menciona nada de esto? Ese es el hallazgo, no la explicación. La referencia completa de configuración tiene 226.200 caracteres de texto y contiene la palabra «daemon» cero veces, mientras documenta check_for_update_on_startup cuatro veces, incluida la advertencia de ponerlo en false solo cuando las actualizaciones se gestionan de forma centralizada. El docs/config.md del repositorio es una redirección de 726 bytes a esas páginas. Sea cual sea el motivo, el efecto práctico es que las salidas circulan como folclore de foro.
La conclusión que sostiene el código
Un pull request de diez líneas, fusionado por un bot tres días antes del lanzamiento, le dio al CLI de Codex un servidor persistente en segundo plano, con autoactualizador y una copia gestionada de 373 a 431 MB que nadie purga, y lo describió en una frase que no menciona ninguna de esas propiedades. Diecisiete días, 24 versiones estables y 129 reacciones después, la bandera sigue diciendo default_enabled: true, la referencia de configuración sigue sin usar la palabra «daemon» en 226.200 caracteres y el código sigue sin política de retención para las versiones que acumula.
Lo que hace interesante este caso no es que un daemon sea intrínsecamente malo. El caso de uso del control remoto es real, hay terceros construyendo harnesses sobre él y los ingenieros de OpenAI pasaron los diecisiete días siguientes publicando arreglos cuidados: bloqueos, identidad por hash, negativa a sobrescribir, recuperación tras borrar el directorio de trabajo, registros del actualizador en los diagnósticos. El hueco es más estrecho y más instructivo que el escándalo: el endurecimiento fue minucioso y la divulgación no, y ambas cosas dependen de equipos, criterios de revisión y notas de versión distintos.
Así que el resumen honesto para quien tenga que decidir si se fía de una herramienta en movimiento rápido es que ahora hay que auditar el modelo de procesos igual que se audita la lista de dependencias. Yo no me enteré de que Codex arranca un daemon leyendo la documentación de OpenAI: me enteré por el informe de error de un desconocido, y lo confirmé leyendo un archivo de un crate de Rust que ninguna página enlaza. Esa inversión —que la descripción autorizada de un comportamiento por defecto ya publicado viva en el código fuente y no en la documentación— es el hallazgo que me llevaría a la siguiente herramienta, y la razón de que todas las cifras de esta auditoría sean reproducibles: el registro de npm da cada tamaño, GitHub da cada etiqueta y cada reacción, y el crate dice a qué se niega a borrar.