2 points par GN⁺ 2026-01-15 | 1 commentaires | Partager sur WhatsApp
  • Lors de l’utilisation de l’API GitHub, un problème d’incompatibilité d’ID a empêché le bon fonctionnement d’une fonction de génération de liens vers des commentaires de PR
  • L’enquête a révélé que GitHub utilise en parallèle deux systèmes d’ID : le node ID de GraphQL et le database ID de l’API REST
  • Après décodage base64 du node ID, il a été confirmé que les 32 bits de poids faible contiennent le database ID, ce qui permet une conversion via une simple opération de masque binaire
  • Une analyse supplémentaire a montré que GitHub mélange un nouveau format d’ID basé sur MessagePack et un ancien format textuel
  • Cette structure met en évidence la dualité du système d’identification interne des objets de GitHub et demande de la prudence lors de l’intégration des API

Découverte du système à double ID de GitHub

  • Lors du développement d’une fonctionnalité de l’outil de revue de code par IA de Greptile, un problème est apparu : les liens vers les commentaires de PR sur GitHub ne fonctionnaient pas
    • L’ID du commentaire enregistré avait bien été injecté dans l’URL, mais un clic ne menait pas à la page GitHub attendue
  • La documentation GitHub montre que le node ID de l’API GraphQL et le database ID de l’API REST reposent sur deux systèmes distincts
    • Exemple de node ID : PRRC_kwDOL4aMSs6Tkzl8
    • Exemple de database ID : 2475899260
  • Le node ID est une chaîne encodée en base64 servant à identifier globalement un objet dans GitHub, tandis que le database ID est utilisé comme identifiant entier dans les URL

Analyse de la relation entre node ID et database ID

  • En comparant les node ID et database ID de plusieurs commentaires de PR, il a été constaté que les deux valeurs augmentent selon un intervalle régulier
  • Le décodage de la partie base64 du node ID produit un entier de 96 bits, dont les 32 bits de poids faible correspondent au database ID
    • Exemple : PRRC_kwDOL4aMSs6Tkzl8 → 32 bits de poids faible = 2475899260
    Publicité
  • Il est possible d’extraire le database ID avec une simple opération de masque binaire
    • Conversion via une expression du type decoded & ((1 << 32) - 1)

Le format d’ID legacy de GitHub

  • En décodant le node ID d’un ancien dépôt (torvalds/linux), on obtient une chaîne dans un format différent
    • Exemple : MDEwOlJlcG9zaXRvcnkyMzI1Mjk4010:Repository2325298
  • Ce format suit la structure [numéro de type d’objet]:[nom de l’objet][Database ID] et constitue un identifiant explicite basé sur une chaîne
  • Dans le cas d’un objet arbre, on obtient une forme comme 04:Tree2325298:7201bfb9..., qui inclut à la fois l’ID du dépôt et la valeur SHA
  • GitHub utilise à la fois le format legacy et le nouveau format, selon le type d’objet et sa date de création
Publicité

Structure du nouveau format de node ID

  • Le guide de migration GraphQL de GitHub indique qu’il faut traiter le node ID comme une chaîne opaque, mais une structure interne existe bien
  • Après décodage base64 puis dépaquetage avec MessagePack, les données apparaissent sous forme de tableau
    • Exemple : [0, 47954445, 2475899260]
  • Composition du tableau
    • Premier élément (0) : probablement un identifiant de version
    • Deuxième élément (47954445) : le database ID du dépôt
    • Troisième élément (2475899260) : le database ID de l’objet
  • La longueur du tableau varie selon le type d’objet : les commits incluent le SHA, tandis que les dépôts ne contiennent que deux éléments

Utilisation pratique et conclusion

  • Exemple de code Python pour extraire le database ID à partir d’un nouveau node ID
    import base64, msgpack
    def node_id_to_database_id(node_id):
        prefix, encoded = node_id.split('_')
        packed = base64.b64decode(encoded)
        array = msgpack.unpackb(packed)
        return array[-1]
    
  • Cette méthode permet d’extraire directement le database ID des commentaires de PR et de résoudre le problème des liens d’URL
  • GitHub maintient actuellement à la fois un nouveau système d’ID basé sur MessagePack et un ancien système textuel
  • Cette structure illustre le processus de transition interne de GitHub et ses efforts de compatibilité, et les développeurs utilisant l’API doivent faire attention aux différences de format d’ID

1 commentaires

 
GN⁺ 2026-01-15
Commentaires sur Hacker News
  • Le GitHub global node ID récent peut être forcé via l’en-tête 'X-Github-Next-Global-ID'
    L’ID se compose du préfixe de type de l’objet et d’une payload msgpack encodée en base64
    Par exemple, mon ID utilisateur "U_kgDOAAhEkg" se décode en [0, 541842], ce qui correspond au databaseId de l’API REST
    Mais il vaut mieux ne pas dépendre de cette implémentation interne et interroger directement le champ databaseId de l’API GraphQL
    Documentation associée : guide de migration des GraphQL global node IDs, mes informations utilisateur GitHub, exemple de décodage CyberChef, implémentation GitHub ETag

  • Décoder les choses de cette façon me paraît fragile
    Les global node IDs de GraphQL sont censés être opaques à l’origine
    Plusieurs types GitHub (PullRequest, etc.) exposent un champ databaseId, donc c’est celui qu’il faut utiliser
    La plupart des API GraphQL encodent en base64 le nom du type et l’ID de base de données, mais rien ne garantit que cette convention sera toujours conservée
    Références : documentation de l’objet PullRequest, spécification des global IDs GraphQL

    • Les types GraphQL de GitHub ont des champs comme permalink, url et l’interface UniformResourceLocatable, donc il n’est pas nécessaire de construire les URL soi-même
    • Ce genre de structure interne a de fortes chances de casser avec le temps
      C’est pour cela que l’API fournit des permalinks. Les ID ou les modèles de liens peuvent changer à tout moment
    • Si l’on veut mettre des métadonnées dans un identifiant, il vaut mieux les chiffrer pour éviter que les utilisateurs ne dépendent de la structure interne
      Ce genre d’approche est aussi fréquent pour les tokens de pagination
  • Des IDs comme 010:Repository2325298 ont une structure claire
    010 est un enum de type, Repository est le nom, 2325298 est l’ID de base de données
    Autrement dit, c’est une forme de préfixe de longueur. Repository fait 10 caractères, Tree en fait 4

    • Ça fait penser au protocole BitTorrent
    • Ça ressemble presque à un URN
  • Opus 4.5 connaît cette astuce de décodage des IDs GitHub et écrit automatiquement le code pour les décoder

  • Ce que l’auteur a découvert est techniquement exact, mais ce n’est ni documenté ni supporté
    GitHub a déjà modifié discrètement la structure interne des node IDs par le passé
    S’ils ajoutent des champs au tableau MessagePack, changent l’encodage, chiffrent le tout ou passent à des UUID,
    les systèmes qui dépendent de cette structure interne cassent immédiatement

  • Les seuls identifiants GitHub que je stocke explicitement sont des clés URL immuables (numéros d’issue/PR ou hash de commit)
    Pour les IDs de commentaire, je les mets simplement dans un blob JSON
    Pas besoin de tout normaliser. JSON est largement assez rapide
    Tant qu’on ne fait pas de requêtes croisées au niveau du commentaire, cela devient rarement un vrai problème de performance

    • Mais les URL d’issue/PR ne sont pas immuables
      Si un dépôt est renommé ou déplacé vers une autre organisation, l’URL peut changer
  • Dans l’ancienne API v3, il n’y avait pas d’ID, donc si quelqu’un changeait son nom d’utilisateur ou le nom d’un dépôt, il devenait difficile de savoir de qui il s’agissait
    J’ai donc implémenté moi-même un système de gestion de propriété par équipe
    Le provider Terraform n’était pas terrible, et à l’offboarding on se retrouvait souvent avec des problèmes du genre « la seule personne admin est partie »
    Tous les dépôts appartiennent à des équipes, et les droits d’accès ne sont accordés qu’au niveau des équipes

    • Penser en termes de « donner l’accès à une équipe, et l’utilisateur en fait partie » plutôt que « donner l’accès à un utilisateur » est bien plus efficace
      Ce contrôle d’accès basé sur les équipes est utile non seulement sur GitHub, mais aussi dans d’autres systèmes
  • C’est un cas classique de Hyrum’s Law — dès que des gens commencent à dépendre d’un comportement non documenté, ça finit par casser

  • En conception de base de données, on expose généralement à l’extérieur une clé naturelle opaque et on utilise en interne un ID entier incrémental

    • Il y a deux raisons à cela
      1. ne pas révéler le nombre d’objets au monde extérieur
      2. empêcher de parcourir tous les objets simplement en incrémentant les IDs
        Mais avec des IDs composites, ces problèmes sont atténués
        Par exemple, si l’ID d’un dépôt inclut l’ID de l’objet, incrémenter l’ID ne permet d’explorer que les objets de ce même dépôt
        Si on y mélange de l’entropie ou un timestamp, l’abus devient pratiquement impossible
    • Mais une clé naturelle peut changer
      Il est donc plus sûr d’exposer une clé de substitution (surrogate key) sans signification
      Par exemple, YouTube peut utiliser en interne des numéros d’index, tout en exposant à l’extérieur des IDs sous forme de codes sans signification
  • Je comprends maintenant pourquoi l’équipe GitHub a fortement étendu ces dernières années le support sharded/multi-database dans Rails