Tous les objets de GitHub ont deux ID
(greptile.com)- 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
- Exemple de node ID :
- 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
- Exemple :
- 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)
- Conversion via une expression du type
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 :
MDEwOlJlcG9zaXRvcnkyMzI1Mjk4→010:Repository2325298
- Exemple :
- 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
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]
- Exemple :
- 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
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 audatabaseIdde l’API RESTMais il vaut mieux ne pas dépendre de cette implémentation interne et interroger directement le champ
databaseIdde l’API GraphQLDocumentation 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 champdatabaseId, donc c’est celui qu’il faut utiliserLa 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
permalink,urlet l’interfaceUniformResourceLocatable, donc il n’est pas nécessaire de construire les URL soi-mêmeC’est pour cela que l’API fournit des permalinks. Les ID ou les modèles de liens peuvent changer à tout moment
Ce genre d’approche est aussi fréquent pour les tokens de pagination
Des IDs comme
010:Repository2325298ont une structure claire010est un enum de type,Repositoryest le nom,2325298est l’ID de base de donnéesAutrement dit, c’est une forme de préfixe de longueur. Repository fait 10 caractères, Tree en fait 4
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
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
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
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
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