- Contexte : renforcement d’un outil Wayland STT local.
- Symptôme : échec du collage synthétique dans 80 % des cas.
- Cause profonde : une condition de concurrence lors de la détection des périphériques Linux
uinput. - Solution : un clavier virtuel persistant intégré au processus avec initialisation différée.
- La philosophie : déboguer avec l’IA lorsque votre dette cognitive l’emporte sur votre modèle mental.
Tout a commencé avec une bonne intention : essayer de renforcer une base de code en inspectant ses faiblesses architecturales et ses anti-modèles.
J’ai créé parakeet-stt, un outil de transcription push-to-talk entièrement local pour Linux. Il suffit de maintenir la touche Ctrl droite enfoncée, de parler, de la relâcher, et le texte transcrit est injecté via uinput à l’endroit où se trouve le curseur. Il dispose d’une interface Rust qui gère le raccourci clavier et la superposition, et d’un démon Python qui exécute le modèle NeMo Parakeet de NVIDIA.
À l’aide d’une invite personnalisée, j’ai demandé à un agent IA d’examiner la base de code. L’une des suggestions était de déléguer l’injection de texte à un processus enfant. L’idée était de lancer un processus, de lui faire émuler un clavier virtuel pour coller le texte, puis de le fermer, afin de garantir que l’insertion soit complètement dissociée du reste du code.
Je l’ai mis en œuvre. Puis je me suis rendu compte que le collage était devenu incroyablement peu fiable, échouant silencieusement environ 80 % du temps.
J’ai passé les trois jours suivants à brûler des tokens et des crédits pour traquer un bug que j’avais introduit afin de résoudre un problème que je n’avais pas réellement. Voici ce que j’ai appris sur les compositeurs Wayland, Linux uinput, et le débogage lorsque les meilleurs modèles disponibles ne parviennent pas à trouver la cause profonde.
Le mode de défaillance était spécifique : la dictée brute simple produisait avec succès des transcriptions et mettait à jour le presse-papiers du système, mais le collage automatique dans les applications cibles (Ghostty, Brave, Zed) échouait silencieusement. Curieusement, le collage manuel à partir du presse-papiers immédiatement après fonctionnait parfaitement.
Lorsque vous ne comprenez pas un problème, vous êtes tenté de faire des suppositions. J’ai demandé à GPT 5.4 et à Amp’s Oracle de produire une liste exhaustive de toutes les causes possibles, classées par probabilité, avec pour chaque entrée les arguments pour et contre basés sur des preuves tirées du code source. Nous avons d’abord supposé que le collage se déclenchait trop tôt après le relâchement de la touche de raccourci, j’ai donc ajouté un délai de stabilisation de 250 ms. Cela n’a pas résolu le problème.
J’ai alors soupçonné un décalage de focus: peut-être que le processus enfant acheminait l’accord synthétique vers la mauvaise surface. J’ai ajouté une fonctionnalité d’observabilité pour capturer des instantanés du focus côté enfant, ce qui a prouvé que la fenêtre correcte était bien sélectionnée.
La preuve irréfutable
Après avoir rayé ces deux éléments de la liste, j’ai décidé de créer une suite de tests complète et un script paste-gap-matrix pour évaluer le pipeline de bout en bout, en testant chaque backend et chaque mode de configuration.
Les données ont révélé une énorme divergence :
uinputen injection seule (sans cycle de vie push-to-talk) : taux de réussite supérieur à 95 %.uinputen flux PTT (générant un nouveau processus par injection) : taux de réussite de 20 %.
Le problème ne venait pas du backend uinput lui-même. Il venait du cycle de vie du clavier virtuel.
Lorsque le flux PTT générait un nouveau sous-processus, il créait un tout nouveau clavier virtuel /dev/uinput, émettait immédiatement la combinaison Ctrl+Shift+V et détruisait le périphérique lorsque le processus se terminait.
La documentation Linux uinput met explicitement en garde contre ce schéma : après UI_DEV_CREATE, l’espace utilisateur a besoin de temps pour détecter le nouveau périphérique. Les compositeurs Wayland tels que COSMIC et Smithay traitent les connexions à chaud des périphériques en classant le périphérique et en l’associant à un siège. Si un périphérique n’est encore connu d’aucun siège, ses événements clavier sont ignorés silencieusement.
En créant et en détruisant le périphérique presque instantanément, je devançais la phase de découverte du compositeur. Les interprétations partielles de l’accord pendant le préchauffage du périphérique expliquaient également pourquoi les premiers et derniers caractères étaient parfois tronqués.
La solution
La solution consistait à supprimer le sous-processus et à passer à un émetteur uinput persistant intégré au processus.
Au lieu de créer un périphérique par tâche, le système utilise désormais une initialisation différée :
- Il crée le clavier virtuel
/dev/uinputlors de la première tentative de collage. - Il applique un délai de préchauffage limité uniquement après une nouvelle création, afin de permettre au compositeur Wayland de découvrir et d’attribuer un siège au périphérique.
- Il maintient le périphérique actif pour les tâches suivantes tant qu’il reste en bon état.
Cela préserve la fiabilité en maintenant le périphérique actif, tout en permettant une récupération tardive de /dev/uinput via un backoff limité si la création du périphérique échoue.
Modèles mentaux minimaux
La leçon intemporelle à retenir ici est simple : ne résolvez pas un problème que vous n’avez pas encore. Je voulais rendre l’architecture plus robuste, mais j’ai complètement négligé les conséquences secondaires liées à la création d’un nouveau sous-processus et d’un nouveau clavier virtuel pour chaque injection.
Mais le plus intéressant est la manière dont ce problème a été résolu. Mon modèle mental du système n’était pas en phase avec la réalité, un cas classique de dette cognitive. Je ne comprenais pas entièrement la pile d’entrée Wayland sous-jacente, mais j’ai quand même persévéré.
J’ai traité le système comme une boîte noire et je l’ai débogué à l’aveuglette :
- Dresser la liste des causes profondes potentielles et demander à un modèle SOTA d’attribuer des scores de probabilité.
- Se concentrer uniquement sur l’ajout d’observabilité et de tests, sans modifier le comportement.
- Conserver un seul fichier Markdown documentant tous les faits, les résultats des tests et les preuves sans opinion. Continuer à ajouter des informations à ce fichier canonique à mesure que vous approfondissez vos recherches.
- Alimenter le modèle de raisonnement le plus performant disponible au début de chaque session avec les données brutes. Mettre en œuvre la prochaine étape présentant le meilleur retour sur investissement, répéter l’opération.
Était-ce un gaspillage inefficace de tokens ? À 100 %. Si j’avais tout codé à la main et conservé un modèle mental parfait, ce bug n’aurait probablement pas existé.
Mais nous assistons à un changement de paradigme. Le code artisanal et le maintien d’une compréhension systémique parfaite deviennent comme l’équitation: un moyen de transport charmant qui n’est plus strictement nécessaire. La véritable compétence consiste à apprendre à tirer parti de l’ingénierie agentique pour déboguer et stabiliser systématiquement les systèmes sans encourir la charge cognitive traditionnellement associée à cette tâche.