root-cause-tracing

Par microsoft · fluidframework

À utiliser lorsque des erreurs surviennent en profondeur dans l'exécution et que vous devez remonter jusqu'au déclencheur d'origine — trace les bugs en remontant systématiquement la pile d'appels, en ajoutant de l'instrumentation si nécessaire, afin d'identifier la source des données invalides ou du comportement incorrect.

npx skills add https://github.com/microsoft/fluidframework --skill root-cause-tracing

Traçage de la cause racine

Vue d'ensemble

Les bugs se manifestent souvent profondément dans la pile d'appels (git init dans le mauvais répertoire, fichier créé au mauvais endroit, base de données ouverte avec le mauvais chemin). Votre instinct est de corriger où l'erreur apparaît, mais c'est traiter un symptôme.

Principe fondamental : Remontez la chaîne d'appels jusqu'à trouver le déclencheur original, puis corrigez à la source.

Quand l'utiliser

digraph when_to_use {
    "Bug appears deep in stack?" [shape=diamond];
    "Can trace backwards?" [shape=diamond];
    "Fix at symptom point" [shape=box];
    "Trace to original trigger" [shape=box];

    "Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"];
    "Can trace backwards?" -> "Trace to original trigger" [label="yes"];
    "Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"];
}

Utilisez quand :

  • L'erreur se produit profondément dans l'exécution (pas au point d'entrée)
  • La trace de pile affiche une longue chaîne d'appels
  • Il n'est pas clair d'où provenaient les données invalides
  • Vous devez trouver quel test/code déclenche le problème

Le processus de traçage

1. Observer le symptôme

Error: git init failed in /Users/jesse/project/packages/core

2. Trouver la cause immédiate

Quel code cause directement cela ?

await execFileAsync('git', ['init'], { cwd: projectDir });

3. Demander : Qui a appelé cela ?

WorktreeManager.createSessionWorktree(projectDir, sessionId)
  → called by Session.initializeWorkspace()
  → called by Session.create()
  → called by test at Project.create()

4. Continuer à remonter

Quelle valeur a été passée ?

  • projectDir = '' (chaîne vide !)
  • Une chaîne vide comme cwd se résout en process.cwd()
  • C'est le répertoire du code source !

5. Trouver le déclencheur original

D'où provient la chaîne vide ?

const context = setupCoreTest(); // Returns { tempDir: '' }
Project.create('name', context.tempDir); // Accessed before beforeEach!

Ajouter des traces de pile

Quand vous ne pouvez pas tracer manuellement, ajoutez de l'instrumentation :

// Before the problematic operation
async function gitInit(directory: string) {
  const stack = new Error().stack;
  console.error('DEBUG git init:', {
    directory,
    cwd: process.cwd(),
    nodeEnv: process.env.NODE_ENV,
    stack,
  });

  await execFileAsync('git', ['init'], { cwd: directory });
}

Important : Utilisez console.error() dans les tests (pas logger - peut ne pas s'afficher)

Exécutez et capturez :

npm test 2>&1 | grep 'DEBUG git init'

Analysez les traces de pile :

  • Recherchez les noms de fichiers de test
  • Trouvez le numéro de ligne déclenchant l'appel
  • Identifiez le motif (même test ? même paramètre ?)

Trouver quel test cause la pollution

Si quelque chose apparaît pendant les tests mais vous ne savez pas quel test :

Utilisez le script de bisection : @find-polluter.sh

./find-polluter.sh '.git' 'src/**/*.test.ts'

Exécute les tests un par un, s'arrête au premier pollueur. Voir le script pour l'utilisation.

Exemple réel : projectDir vide

Symptôme : .git créé dans packages/core/ (code source)

Chaîne de traçage :

  1. git init s'exécute dans process.cwd() ← paramètre cwd vide
  2. WorktreeManager appelé avec projectDir vide
  3. Session.create() passée une chaîne vide
  4. Test accédé à context.tempDir avant beforeEach
  5. setupCoreTest() renvoie { tempDir: '' } initialement

Cause racine : Initialisation de variable de haut niveau accédant à une valeur vide

Correction : Fait de tempDir un getter qui lance une exception s'accédé avant beforeEach

Également ajouté une validation à plusieurs niveaux :

  • Couche 1 : Project.create() valide le répertoire
  • Couche 2 : WorkspaceManager valide non vide
  • Couche 3 : Garde NODE_ENV refuse git init en dehors de tmpdir
  • Couche 4 : Journalisation de trace de pile avant git init

Principe clé

digraph principle {
    "Found immediate cause" [shape=ellipse];
    "Can trace one level up?" [shape=diamond];
    "Trace backwards" [shape=box];
    "Is this the source?" [shape=diamond];
    "Fix at source" [shape=box];
    "NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];

    "Found immediate cause" -> "Can trace one level up?";
    "Can trace one level up?" -> "Trace backwards" [label="yes"];
    "Can trace one level up?" -> "NEVER fix just the symptom" [label="no"];
    "Trace backwards" -> "Is this the source?";
    "Is this the source?" -> "Trace backwards" [label="no - keeps going"];
    "Is this the source?" -> "Fix at source" [label="yes"];
}

NE corrigez JAMAIS juste où l'erreur apparaît. Remontez pour trouver le déclencheur original.

Conseils pour les traces de pile

Dans les tests : Utilisez console.error() pas logger - logger peut être supprimé Avant l'opération : Journalisez avant l'opération dangereuse, pas après son échec Incluez le contexte : Répertoire, cwd, variables d'environnement, timestamps Capturez la pile : new Error().stack affiche la chaîne d'appels complète

Impact dans le monde réel

D'une session de débogage (2025-10-03) :

  • Cause racine trouvée via un traçage de 5 niveaux
  • Correction à la source (validation getter)
  • 4 couches de défense ajoutées
  • 1847 tests réussis, zéro pollution

Skills similaires