Un circuit breaker de cuota no basta: cómo decidir qué trabajos relanzar en launchd
El autor de claude-quota-guard.py publica quota-catchup.py, la segunda mitad del problema: qué hacer con los trabajos que el circuito se saltó en launchd.

Un circuit breaker que corta todos los trabajos en cuanto se agota la cuota resuelve la mitad del problema. La otra mitad aparece después: cuando el circuito vuelve a cerrarse, todo lo que se saltó sigue sin ejecutarse. El autor de claude-quota-guard.py ha publicado la pieza que faltaba, quota-catchup.py, para decidir qué relanzar tras la recuperación.
El guardián se coloca delante de 15 de los plists de ~/Library/LaunchAgents. Mientras el circuito está abierto, cada trabajo que arranca devuelve exit 0 sin ejecutar nada y deja un marcador CLAUDE_QUOTA_JOB_SKIPPED en stderr con la etiqueta y los segundos restantes. El fallo de fondo es que launchd no reintenta por su cuenta: espera al siguiente StartCalendarInterval. Si el trabajo de las 9:00 se saltó por cuota y esta se recupera a las 10:00, pero el siguiente hueco es mañana a las 9:00, se pierde un día entero.
Tres filtros encadenados con AND
find_candidates recorre los plists ordenados y solo acepta un trabajo como candidato si supera tres condiciones a la vez. La primera, is_guarded, es una comparación de cadenas sobre ProgramArguments buscando la presencia de claude-quota-guard: sirve para no arrastrar trabajos cron normales que nada tienen que ver con la cuota. La segunda, latest_marker_is_today_skip, descarta lo que ya se ejecutó y los marcadores antiguos. La tercera, calendar_slot_passed, es la interesante.
Si el plist declara StartInterval, devuelve falso: esos trabajos se repiten solos en el siguiente intervalo y no necesitan rescate. Para los de calendario recorre todas las entradas y va acumulando con un OR si alguna ha pasado ya hoy. El autor avisa del error clásico de quedarse con el veredicto de la primera entrada o de la última: con huecos a las 9:00 y a las 18:00 evaluados a las 13:00, la respuesta correcta es que sí es candidato, porque el de la mañana ya pasó. Un caso real con varios huecos es com.shun.daily-brief.plist, con entradas a las 8:00 y a las 10:30.
El timeout que se quedó corto
El relanzamiento pasa por kick_and_wait, que lanza launchctl kickstart y sondea hasta que el trabajo sale de la gestión de launchd. El valor inicial era 1200 segundos y en producción no llegaba. Una medición del 21 de agosto dejó claro que las rutas de generación con claude, como affameba-gen, pasan de los 20 minutos, así que el número bueno es 2700. Con la configuración equivocada el load average llegó a superar 40, según cuenta el propio autor.
La entrada no enlaza un repositorio concreto con los dos scripts, así que lo aprovechable hoy es el patrón más que el código: si tienes tareas programadas detrás de un cortacircuitos por cuota o por cualquier otro recurso compartido, el cierre del circuito es la parte fácil, y la política de recuperación es donde se decide si el sistema se comporta o se atropella a sí mismo. Queda por ver si el autor publica los ficheros o si esto se queda en descripción de diseño.
