[performance] cargo install cargo-deny --locked recompile l'outil à chaque pipeline, ce qui ajoute plusieurs minutes de CI. Il vaut mieux utiliser une image préconstruite (ghcr.io/embarkstudios/cargo-deny) ou un binaire précompilé. Par ailleurs les étapes CI utilisent rust:1.99 alors que le Containerfile build avec rust:1.98-trixie : les versions de toolchain divergent.
[maintainability] Le socket podman est monté avec le chemin en dur /run/user/1000/podman/podman.sock. Le devcontainer ne démarre pas pour un utilisateur dont l'UID n'est pas 1000, alors que le build utilise ${localEnv:UID}. De plus, si le socket n'existe pas, le montage fait échouer l'ouverture du conteneur. Il faut utiliser ${localEnv:XDG_RUNTIME_DIR}/podman/podman.sock.
[bug] Le bouton « Watch activity » est affiché aussi pour les jobs pending. Or /jobs/{id}/events renvoie 404 tant que le job n'a pas démarré (le canal n'est créé que par EventBus::start). Le panneau affiche alors « stream closed » sans explication. Il faut masquer ou désactiver le bouton pour les jobs en attente, ou ré-ouvrir le flux au passage en started.
[bug] Le flux /events reconnecte automatiquement (EventSource), mais les événements émis pendant la coupure (redémarrage serveur, réseau) sont perdus, sans événement gap. Le dashboard garde alors un état périmé (jobs fantômes dans Pending/In progress). Il faut brancher onOpen pour appeler refresh() à chaque (re)connexion, y compris la première.
[bug] Dans la branche « liste vide », render() ajoute un <p class="empty">, mais la branche non vide ne le retire jamais. Après la première apparition d'un job (liste vide puis job), le message « No pending job. » reste affiché au-dessus ou au-dessous des cartes. Il faut supprimer l'élément .empty dans la branche non vide, ou faire un replaceChildren avant de ré-attacher les cartes.
[performance] Contrairement aux arguments (tronqués à 200 caractères), output est conservé en entier, jusqu'à 32 Ko par appel d'outil. L'historique garde 256 événements par job, soit environ 8 Mo par job actif. Chaque nouvel abonné SSE reçoit ensuite tout cet historique. Il faut tronquer l'aperçu envoyé au dashboard (comme pour MAX_EVENT_ARGUMENTS), ce qui limite aussi ce qui fuit sur une route publique.
[performance] list() charge toutes les reviews, y compris la colonne markdown, sans pagination ni tri, et /reviews les renvoie toutes d'un coup. La table ne fait que grossir et chaque ouverture ou rafraîchissement du dashboard relit tout l'historique. Il faut une limite (par exemple les N dernières, triées par completed_at DESC), ou omettre markdown dans la liste.
[bug] Le contrôle is_known puis queue.push n'est pas atomique. Deux livraisons concurrentes du même événement (retry Gitea) passent toutes les deux le contrôle. La seconde échoue sur la contrainte UNIQUE(event_id) et renvoie 503 Could not enqueue task, avec un faux événement Sentry et le compteur enqueue_failed. Gitea risque alors de re-livrer en boucle. Il faut traiter la violation d'unicité comme AlreadyProcessedErr (200), ou faire un insert avec ON CONFLICT DO NOTHING.
[security] Les routes /reviews, /jobs, /events et /jobs/{id}/events sont publiques, sans authentification. Elles exposent les noms de dépôts privés, les numéros de PR, le payload complet des webhooks (corps des commentaires, clone_url), le markdown des reviews et le flux des appels d'outils de l'agent. Ce flux contient des extraits du code des dépôts, ce qui est sensible. Le service tourne sur 0.0.0.0 et le dashboard est servi par le même processus. Il faut protéger ces routes (token ou basic auth, ou reverse proxy authentifié), ou au moins les placer sur un port interne distinct du /webhook. Il n'y a aussi aucune limite sur le nombre de connexions SSE ouvertes.
[bug] Le gestionnaire ferme l’EventSource à la première erreur, y compris lors d’une coupure réseau temporaire pour laquelle EventSource aurait normalement réessayé. L’utilisateur perd alors les mises à jour du job. Fermez-le seulement quand la fin du job est confirmée ou quand le serveur indique que le job n’existe plus.
[bug] En cas d’échec de mark_processing, le job est tout de même traité. Il reste alors Pending en base et pourra être repris après redémarrage — ou par une autre instance — pendant que cette exécution peut déjà publier la review. N’exécutez pas le job si sa transition d’état échoue.
[bug] La sélection des jobs Pending et leur passage à Processing sont deux opérations distinctes, sans réservation atomique. Avec plusieurs instances, chacune peut récupérer le même job et lancer la même review avant que l’autre ne le marque Processing. Il faut réclamer les jobs de façon atomique.
[bug] Le test is_known et l’insertion dans la file ne sont pas atomiques. Deux livraisons simultanées du même webhook peuvent toutes deux passer le test ; la contrainte unique fait ensuite échouer l’une et renvoie 503 au lieu de traiter ce doublon comme déjà accepté. Gérez explicitement ce conflit d’insertion.
[security] Ces routes de consultation et les flux SSE sont accessibles sans authentification. Elles exposent notamment les noms de dépôts, les reviews et les sorties des outils exécutés sur le code, qui peuvent contenir des données de dépôts privés. Protégez ces endpoints ou limitez explicitement leur accès.
[performance] Review::all() charge toutes les reviews et leurs champs, notamment le Markdown, à chaque rafraîchissement du dashboard. Cette requête et la réponse JSON grossissent sans limite avec l’historique. Ajoutez une pagination ou une limite et ne chargez que les champs nécessaires à la liste.
[bug] refresh() est lancé sans attendre avant l’abonnement SSE. Si un événement arrive pendant le chargement, la réponse REST peut ensuite écraser l’état plus récent fourni par SSE ; aucun nouvel événement n’est garanti pour corriger la liste. Abonnez-vous avant de charger l’état, ou réconciliez la réponse avec les événements reçus entre-temps.
[bug] Le guide demande de configurer neuf variables, mais omet DATABASE_URL, désormais obligatoire, puis passe directement au lancement sans expliquer comment appliquer les migrations. Un nouvel utilisateur suivant ces étapes ne peut pas démarrer le service. Ajoutez la configuration de PostgreSQL et l’étape migration apply.