pull_request.synchronize re-encola análisis sobre ramas propiasEl webhook router en src/index.ts encola un análisis completo para los eventos push, pull_request.opened y pull_request.synchronize sin distinguir si el autor del push es la propia GitHub App. Cuando el worker crea una PR de sugerencias, GitHub emite un pull_request.synchronize sobre esa PR recién creada — lo que dispara un nuevo análisis, que crea otra PR, generando un bucle autorreferencial.
Añadir guard al inicio del handler: si payload.sender.type === 'Bot' o payload.sender.login termina en [bot], retornar sin encolar. Adicionalmente, comprobar si el PR head branch sigue el patrón de ramas generadas por la app (codesight/suggestions-*) y rechazarlo como ingress.
En src/billing/quota.ts la función checkQuota(userId) consulta usage_reservations y compara con el plan. Pero el incrementUsage() solo ocurre dentro del worker, después de llamar a Anthropic. Bajo carga concurrente (varios pushes simultáneos del mismo repo), múltiples requests pasan el gate con cuota disponible y todas inician análisis — superando el límite del plan antes de que ninguna haya terminado de incrementar.
Convertir el gate en una reserva optimista atómica con INSERT INTO usage_reservations ... ON CONFLICT DO UPDATE SET count = count + 1 WHERE count < limit RETURNING id. Si el INSERT no devuelve fila, rechazar el enqueue. Liberar la reserva si el worker falla antes de consumir tokens.
claude-opus-4-5En src/ai/model-router.ts la selección del modelo lee la variable de entorno ANTHROPIC_OPUS_KEY. Si la clave existe (como ocurre en producción Railway), todos los usuarios — incluyendo los del plan Free — pasan por la rama premium. El campo subscription.tier no se evalúa en esa función.
Añadir condición: if (process.env.ANTHROPIC_OPUS_KEY && ctx.subscription.tier === 'premium'). Añadir test unitario que verifique que un usuario Free con OPUS_KEY presente en env recibe claude-sonnet-4.
BullMQ permite configurar jobId como clave de deduplicación. Actualmente el worker usa un UUID aleatorio por job — si llegan dos webhooks del mismo evento (GitHub reintenta webhooks fallidos o el cliente hace force-push seguido), el mismo commit SHA se analiza dos veces.
Usar jobId: \`${repoId}:${sha}:${eventType}\` al encolar. BullMQ descartará el segundo job si el primero ya está en cola o procesando. Coste de implementación: ~15 min.
| Prioridad | Fix | Archivos afectados | Ahorro est. / semana | Esfuerzo | Estado |
|---|---|---|---|---|---|
| P0 | Guard bot sender en webhook synchronize | src/index.ts |
$785 | 30 min | Pendiente |
| P0 | Reserva atómica de cuota pre-enqueue | src/billing/quota.ts |
~$0 directo, previene overflow | 2h | Pendiente |
| P1 | Verificar tier antes de asignar Opus | src/ai/model-router.ts |
$349 | 20 min | Pendiente |
| P2 | Deduplicación de jobs por SHA | src/queue/producer.ts |
~$28 | 15 min | Pendiente |
| Total ahorro semanal estimado post-fix | $1.062 | ROI: 1 día de trabajo vs $4.500/mes | |||
Enviar payload con sender.type=Bot al handler local y verificar que no se encola ningún job en BullMQ dashboard.
Lanzar 10 requests paralelas para el mismo usuario en plan Free (límite 5). Verificar que exactamente 5 reservas persisten en DB.
selectModel({ subscription: {tier:'free'} }) debe retornar 'claude-sonnet-4' incluso con ANTHROPIC_OPUS_KEY presente en env.
Encolar dos jobs con mismo SHA, verificar que BullMQ reporta 1 activo y 1 descartado en la UI de Railway.