2 Punkte von GN⁺ 2026-01-15 | 1 Kommentare | Auf WhatsApp teilen
  • Bei der Nutzung der GitHub-API trat in einer Funktion zum Erzeugen von PR-Kommentar-Links ein Problem auf: Wegen eines ID-Mismatches funktionierten die Links nicht.
  • Die Untersuchung ergab, dass GitHub zwei ID-Systeme parallel verwendet: die node ID von GraphQL und die database ID der REST API.
  • Durch Base64-Decodierung der node ID wurde bestätigt, dass die unteren 32 Bit die database ID enthalten, sodass eine Umwandlung per einfacher Bitmasken-Operation möglich ist.
  • Eine weitergehende Analyse zeigte, dass GitHub ein neues ID-Format auf MessagePack-Basis und ein stringbasiertes Legacy-Format gemischt verwendet.
  • Diese Struktur zeigt die Doppelheit des internen Objekt-Identifikationssystems von GitHub und macht deutlich, dass Entwickler bei der API-Integration vorsichtig sein müssen.

Entdeckung des doppelten ID-Systems von GitHub

  • Während der Entwicklung einer Funktion des AI-Code-Review-Tools von Greptile trat ein Problem auf, bei dem GitHub-PR-Kommentar-Links nicht funktionierten.
    • Die gespeicherte Kommentar-ID wurde an die URL angehängt, doch beim Klicken führte sie nicht zur GitHub-Seite.
  • Ein Blick in die GitHub-Dokumentation zeigte, dass die node ID der GraphQL API und die database ID der REST API als unterschiedliche Systeme existieren.
    • Beispiel für eine node ID: PRRC_kwDOL4aMSs6Tkzl8
    • Beispiel für eine database ID: 2475899260
  • Die node ID ist ein Base64-codierter String zur globalen Identifizierung von Objekten auf GitHub, während die database ID als ganzzahliger URL-Bezeichner verwendet wird.

Analyse der Beziehung zwischen node ID und database ID

  • Durch den Vergleich der node IDs und database IDs mehrerer PR-Kommentare wurde bestätigt, dass beide Werte in konstanten Abständen ansteigen.
  • Nach dem Decodieren des Base64-Teils der node ID entstand eine 96-Bit-Ganzzahl, deren untere 32 Bit mit der database ID übereinstimmten.
    • Beispiel: PRRC_kwDOL4aMSs6Tkzl8 → untere 32 Bit = 2475899260
    Anzeige
  • Die database ID lässt sich mit einer einfachen Bitmasken-Operation extrahieren.
    • Die Umwandlung erfolgt mit einer Operation der Form decoded & ((1 << 32) - 1).

GitHubs Legacy-ID-Format

  • Beim Decodieren der node ID eines älteren Repositorys (torvalds/linux) erschien ein String in einem anderen Format.
    • Beispiel: MDEwOlJlcG9zaXRvcnkyMzI1Mjk4010:Repository2325298
  • Dieses Format hat die Struktur [Objekttyp-Nummer]:[Objektname][Database ID] und ist damit ein expliziter stringbasierter Bezeichner.
  • Bei Tree-Objekten hat es die Form 04:Tree2325298:7201bfb9... und enthält zusätzlich die Repository-ID und den SHA-Wert.
  • GitHub verwendet Legacy-Format und neues Format parallel, wobei das Format je nach Objekttyp und Erstellungszeitpunkt unterschiedlich ist.
Anzeige

Die Struktur des neuen node-ID-Formats

  • Der GraphQL-Migrationsleitfaden von GitHub weist darauf hin, node IDs als opake Strings zu behandeln, dennoch existiert eine interne Struktur.
  • Nach Base64-Decodierung und Entpacken mit MessagePack erscheint die Datenstruktur als Array.
    • Beispiel: [0, 47954445, 2475899260]
  • Aufbau des Arrays
    • Erstes Element (0): vermutlich ein Versionskennzeichen
    • Zweites Element (47954445): die database ID des Repositorys
    • Drittes Element (2475899260): die database ID des Objekts
  • Je nach Objekttyp ist die Array-Länge unterschiedlich; Commits enthalten den SHA-Wert, Repositorys enthalten nur zwei Elemente.

Praktische Nutzung und Fazit

  • Beispiel für Python-Code zum Extrahieren der database ID aus einer neuen 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]
    
  • Mit dieser Methode lässt sich die database ID von PR-Kommentaren direkt extrahieren, wodurch sich das Problem mit den URL-Links lösen lässt.
  • GitHub hält derzeit das neue MessagePack-basierte ID-System und das stringbasierte Legacy-System gleichzeitig aufrecht.
  • Diese Struktur zeigt GitHubs internen Übergangsprozess und den Versuch, Kompatibilität zu wahren; Entwickler, die die API nutzen, sollten auf Unterschiede bei den ID-Formaten achten.

1 Kommentare

 
GN⁺ 2026-01-15
Hacker-News-Kommentare
  • Die neueste globale GitHub-Node-ID lässt sich über den Header 'X-Github-Next-Global-ID' erzwingen
    Die ID besteht aus dem Typ-Präfix des Objekts und einer base64-kodierten MsgPack-Payload
    Zum Beispiel wird meine Benutzer-ID "U_kgDOAAhEkg" zu [0, 541842] dekodiert, was mit der databaseId der REST-API übereinstimmt
    Trotzdem sollte man sich nicht auf solche internen Implementierungen verlassen, sondern das Feld databaseId direkt über die GraphQL-API abfragen
    Relevante Dokumentation: Migrationsleitfaden für globale GraphQL-Node-IDs, Meine GitHub-Benutzerinformationen, CyberChef-Dekodierbeispiel, GitHub-ETag-Implementierung

  • Solches Dekodieren halte ich für fragil
    Globale Node-IDs in GraphQL sollten grundsätzlich opaque sein
    Viele GitHub-Typen wie PullRequest stellen das Feld databaseId bereit, also sollte man dieses verwenden
    Die meisten GraphQL-APIs kodieren Typnamen und DB-ID per base64, aber es gibt keine Garantie, dass diese Konvention immer bestehen bleibt
    Siehe auch: PullRequest-Objektdokumentation, GraphQL-Spezifikation für globale IDs

    • GitHub-GraphQL-Typen haben Felder wie permalink und url sowie das Interface UniformResourceLocatable, sodass man URLs nicht selbst zusammensetzen muss
    • Solche internen Strukturen werden mit der Zeit wahrscheinlich brechen
      Genau deshalb bietet die API einen Permalink an. IDs oder Linkmuster können sich jederzeit ändern
    • Wenn man Metadaten in Bezeichnern unterbringen will, sollte man sie besser verschlüsseln, damit Nutzer nicht von der internen Struktur abhängig werden
      So etwas wird auch häufig bei Pagination-Tokens verwendet
  • IDs wie 010:Repository2325298 haben eine klare Struktur
    010 ist das Typ-Enum, Repository der Name und 2325298 die DB-ID
    Es ist also im Prinzip ein Längenpräfix. Repository hat 10 Zeichen, Tree 4

    • Das erinnert an das BitTorrent-Protokoll
    • Es wirkt fast wie eine URN
  • Opus 4.5 kennt diesen GitHub-ID-Dekodiertrick und schreibt automatisch den Dekodiercode dafür

  • Was der Autor gefunden hat, ist technisch korrekt, aber nicht dokumentiert und nicht unterstützt
    GitHub hat die interne Struktur von Node-IDs in der Vergangenheit schon stillschweigend geändert
    Wenn sie dem MessagePack-Array Felder hinzufügen, die Kodierung ändern, verschlüsseln oder auf UUIDs umstellen,
    brechen Systeme, die sich auf diese Interna verlassen, sofort

  • Die einzigen GitHub-Bezeichner, die ich explizit speichere, sind unveränderliche URL-Schlüssel wie Issue-/PR-Nummern oder Commit-Hashes
    Kommentar-IDs packe ich einfach in einen JSON-Blob
    Man muss nicht alles normalisieren. JSON ist schnell genug
    Solange man keine abfrageübergreifenden Queries auf Kommentar-Ebene macht, wird das kaum je zu einem Performanceproblem

    • Allerdings sind Issue-/PR-URLs nicht unveränderlich
      Wenn ein Repository umbenannt oder in eine andere Organisation verschoben wird, ändert sich auch die URL
  • In der alten v3-API gab es keine IDs, daher war es schwer nachzuvollziehen, wer jemand war, wenn Benutzername oder Repository-Name geändert wurden
    Deshalb habe ich selbst ein Ownership-Management-System auf Team-Ebene gebaut
    Der Terraform-Provider war nicht besonders gut, weshalb es beim Offboarding oft Probleme gab wie „die einzige Admin-Person ist gegangen“
    Jedes Repository gehört einem Team, und Zugriffe werden ebenfalls nur auf Team-Ebene vergeben

    • Die Denkweise „Man gibt nicht einem Benutzer Zugriff, sondern einem Team, und der Benutzer ist Mitglied dieses Teams“ ist viel effizienter
      Diese teambasierte Zugriffskontrolle ist nicht nur für GitHub, sondern auch für andere Systeme nützlich
  • Ein klassischer Fall von Hyrum’s Law — sobald Leute anfangen, sich auf undokumentiertes Verhalten zu verlassen, geht es irgendwann kaputt

  • Beim Datenbankdesign stellt man nach außen normalerweise opaque natürliche Schlüssel bereit und verwendet intern hochzählende Integer-IDs

    • Dafür gibt es zwei Gründe
      1. um die Anzahl der Objekte nicht nach außen offenzulegen
      2. um zu verhindern, dass man durch einfaches Hochzählen aller IDs alle Objekte durchiterieren kann
        Mit zusammengesetzten IDs wird dieses Problem aber kleiner.
        Wenn etwa eine Repository-ID die Objekt-ID mit enthält, lassen sich durch Hochzählen nur Objekte innerhalb desselben Repositorys erkunden
        Mischt man noch Entropie oder Zeitstempel hinein, ist Missbrauch fast unmöglich
    • Natürliche Schlüssel können sich jedoch ändern
      Deshalb ist es sicherer, bedeutungslose Surrogate Keys nach außen freizugeben
      YouTube verwendet zum Beispiel intern vielleicht eine Indexnummer, gibt extern aber eine bedeutungslose ID in Codeform aus
  • Jetzt ist verständlich, warum das GitHub-Team in den letzten Jahren die Unterstützung für sharded/multi-database in Rails so stark ausgebaut hat