Cuándo
17-09-2026 (v1.138.1) y 18-09-2026 (v1.138.3) — mismo patrón, dos subsistemas distintos, un día de diferencia.
Síntomas visibles
Visto en PROD al estrenar la función de equipos fuera de servicio (ver [[feature—monitoring—equipos-fuera-de-servicio]]): el Agente recibía la lista de targets actualizados con un cambio de retraso. Al sacar un equipo de servicio, al Agente le seguía llegando con SNMP completo; al devolverlo, le llegaba “solo ping”. El mismo patrón afectaba a TODOS los cambios de targets (editar, borrar, activar monitorización), no solo al botón nuevo — simplemente nadie lo había notado antes porque el retraso de una petición a la siguiente era invisible.
Al día siguiente apareció la misma familia de bug en otro subsistema: las tareas Huey que leen una fila recién creada (DeviceTask, AsyncJob de Auto-Plan/MIB/backup/restore, procesado de medios de Signage, tickets de integridad) podían encolarse y no encontrar la fila — en el peor caso, un DeviceTask quedaba en estado queued para siempre, sin next_retry_at que lo reintentara.
Causa raíz
El middleware de aislamiento entre organizaciones (RLS) envuelve cada petición HTTP en transaction.atomic(). El código que avisaba al Agente (notify_agents_targets_updated) o encolaba la tarea Huey (@db_task(), huey 3.0.1) lo hacía dentro de esa transacción — pero el consumidor WebSocket o el worker de Huey leen la fila por otra conexión a la base de datos, que todavía no ve el cambio porque el COMMIT no ha ocurrido.
Fix aplicado
345aa60e—notify_agents_targets_updatedpasa a registrarse contransaction.on_commit; fuera de una transacción se ejecuta en el acto; si la petición se deshace (rollback), el aviso no sale. 3 tests nuevos.4ec81268— mismo patrón, 8 sitios distintos envueltos entransaction.on_commit:DeviceOps.dispatch(el más grave — tareasqueuedpara siempre),AsyncJob(Auto-Plan, subida de MIB, backup, restore), Signage (process_media_asset), integridad (open_integrity_drift_ticket). Tests existentes envueltos endjango_capture_on_commit_callbacks(ningún assert cambia). No se había observado en PROD — se corrigió por analogía con345aa60e, deuda documentada en task #326 antes del fix.
Lecciones
Cualquier código que encola trabajo asíncrono para otro proceso (WebSocket, Huey, Celery) dentro de una vista Django con RLS activo debe registrarse con transaction.on_commit, nunca ejecutarse en el acto — el otro proceso lee por una conexión distinta y no ve cambios sin confirmar. Es una clase de bug, no un caso aislado: apareció dos veces en dos días en subsistemas sin relación aparente.
Preventivos futuros
Cualquier notify_*, @db_task() o disparo de tarea en segundo plano que dependa de leer una fila recién escrita en la misma petición debe auditarse contra este patrón. Candidato a lint/checklist de revisión de código para nuevas vistas.
Véase también
- [[feature—monitoring—equipos-fuera-de-servicio]]
- [[incident—20260917—agente-tanda-trafico-equipo-mudo-ccib]]
- [[incident—20260915—correo-cambio-primary-ruido-reconexion]]
Referenciado desde
- 57 correos de "cambio de Primary" en 7 días sin un relevo real — reconexión tras micro-corte
- Comando unassign_ghost_profiles — limpieza de fichas fantasma tras un restore
- El "ciclo lento" del Agente en el CCIB no era lentitud — un equipo mudo retenía la tanda de tráfico
- Equipos fuera de servicio — Take out of Service / Return to Service