2 pontos por GN⁺ 2026-01-15 | 1 comentários | Compartilhar no WhatsApp
  • Ao usar a API do GitHub, surgiu um problema em que os links de comentários de PR não funcionavam devido a uma incompatibilidade de IDs
  • A investigação mostrou que o GitHub usa em paralelo dois sistemas de ID: o node ID do GraphQL e o database ID da API REST
  • Ao fazer o decode em base64 do node ID, foi confirmado que os 32 bits inferiores contêm o database ID, tornando a conversão possível com uma simples operação de máscara de bits
  • Uma análise adicional revelou que o GitHub mistura um novo formato de ID baseado em MessagePack com um formato legado baseado em string
  • Essa estrutura mostra a dualidade do sistema interno de identificação de objetos do GitHub e exige cuidado dos desenvolvedores ao integrar APIs

Descoberta do sistema duplo de IDs do GitHub

  • Durante o desenvolvimento de um recurso da ferramenta de revisão de código com IA da Greptile, surgiu um problema em que links para comentários de PR no GitHub não funcionavam
    • O ID do comentário salvo foi colocado na URL, mas ao clicar a página do GitHub não era aberta
  • Ao consultar a documentação do GitHub, verificou-se que o node ID da API GraphQL e o database ID da API REST existem em sistemas diferentes
    • Exemplo de node ID: PRRC_kwDOL4aMSs6Tkzl8
    • Exemplo de database ID: 2475899260
  • O node ID é uma string codificada em base64 usada para identificar objetos globalmente em todo o GitHub, enquanto o database ID é usado como um identificador inteiro em URLs

Análise da relação entre node ID e database ID

  • Ao comparar node IDs e database IDs de vários comentários de PR, foi confirmado que os dois valores aumentam em intervalos consistentes
  • Ao decodificar a parte em base64 do node ID, foi gerado um inteiro de 96 bits, e os 32 bits inferiores desse valor coincidiam com o database ID
    • Exemplo: PRRC_kwDOL4aMSs6Tkzl8 → 32 bits inferiores = 2475899260
  • É possível extrair o database ID com uma simples operação de máscara de bits
    • A conversão pode ser feita com uma operação no formato decoded & ((1 << 32) - 1)

O formato legado de ID do GitHub

  • Ao decodificar o node ID de um repositório antigo (torvalds/linux), apareceu uma string em outro formato
    • Exemplo: MDEwOlJlcG9zaXRvcnkyMzI1Mjk4010:Repository2325298
  • Esse formato segue a estrutura [número do tipo de objeto]:[nome do objeto][Database ID] e é um identificador explícito baseado em string
  • No caso de objetos de árvore, ele aparece como 04:Tree2325298:7201bfb9..., incluindo o ID do repositório e o valor SHA
  • O GitHub usa em paralelo o formato legado e o novo formato, e o formato varia conforme o tipo do objeto e o momento em que foi criado

Estrutura do novo formato de node ID

  • O guia de migração do GraphQL do GitHub diz para tratar o node ID como uma string opaca, mas há uma estrutura interna
  • Depois do decode em base64, ao fazer unpack com MessagePack, surgem dados em forma de array
    • Exemplo: [0, 47954445, 2475899260]
  • Composição do array
    • Primeiro elemento (0): presumivelmente um identificador de versão
    • Segundo elemento (47954445): o database ID do repositório
    • Terceiro elemento (2475899260): o database ID do objeto
  • O comprimento do array varia conforme o tipo de objeto; commits incluem o SHA, enquanto repositórios contêm apenas dois elementos

Uso prático e conclusão

  • Exemplo de código Python para extrair o database ID de um novo 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]
    
  • Com esse método, é possível extrair diretamente o database ID de comentários de PR e resolver o problema dos links de URL
  • Atualmente, o GitHub mantém ao mesmo tempo o novo sistema de IDs baseado em MessagePack e o sistema legado baseado em string
  • Essa estrutura mostra o processo de transição interna e os esforços de compatibilidade do GitHub, e os desenvolvedores que usam a API devem prestar atenção às diferenças de formato dos IDs

1 comentários

 
GN⁺ 2026-01-15
Comentários do Hacker News
  • O GitHub global node ID mais recente pode ser forçado via o header 'X-Github-Next-Global-ID'
    O ID é composto por um prefixo de tipo do objeto e um payload msgpack codificado em base64
    Por exemplo, meu ID de usuário "U_kgDOAAhEkg" é decodificado como [0, 541842], o que corresponde ao databaseId da API REST
    Mas é melhor não depender dessa implementação interna e consultar diretamente o campo databaseId da API GraphQL
    Documentação relacionada: Guia de migração de IDs globais de nó do GraphQL, Minhas informações de usuário do GitHub, Exemplo de decodificação no CyberChef, Implementação de ETag do GitHub

  • Acho frágil decodificar desse jeito
    O global node ID do GraphQL deveria ser opaco (opaque) por definição
    Vários tipos do GitHub, como PullRequest, oferecem o campo databaseId, então o certo é usar isso
    A maioria das APIs GraphQL codifica em base64 o nome do tipo e o ID no banco, mas não há garantia de que essa regra será mantida para sempre
    Referência: Documentação do objeto PullRequest, Especificação de IDs globais do GraphQL

    • Os tipos GraphQL do GitHub têm campos como permalink, url e a interface UniformResourceLocatable, então não há necessidade de montar URLs manualmente
    • Esse tipo de estrutura interna tem grande chance de quebrar com o tempo
      É por isso que a API fornece permalink. IDs e padrões de link podem mudar a qualquer momento
    • Se quiser colocar metadados em identificadores, o ideal é criptografá-los para que os usuários não dependam da estrutura interna
      Esse tipo de abordagem também é muito usado em tokens de paginação
  • IDs como 010:Repository2325298 têm uma estrutura clara
    010 é um enum de tipo, Repository é o nome e 2325298 é o ID no banco
    Ou seja, é um formato de prefixo de comprimento (length prefix). Repository tem 10 caracteres, Tree tem 4

    • Lembra o protocolo BitTorrent
    • Parece quase um URN
  • O Opus 4.5 conhece esse truque de decodificação dos IDs do GitHub e escreve automaticamente o código para decodificá-los

  • O que o autor descobriu está tecnicamente correto, mas não é documentado nem suportado
    O GitHub já mudou silenciosamente a estrutura interna dos node IDs no passado
    Se adicionarem campos ao array MessagePack, mudarem a codificação, criptografarem ou trocarem para um formato baseado em UUID,
    qualquer sistema que dependa dessa estrutura interna quebra imediatamente

  • Os identificadores do GitHub que eu armazeno explicitamente são basicamente chaves de URL imutáveis (números de issue/PR ou hash de commit)
    ID de comentário eu simplesmente coloco dentro de um blob JSON
    Não é necessário normalizar tudo. JSON é rápido o suficiente
    A menos que você faça consultas cruzadas no nível de comentário, isso dificilmente vai aparecer como problema de desempenho

    • Mas URLs de issue/PR não são imutáveis
      Se o repositório mudar de nome ou for transferido para outra organização, a URL pode mudar
  • A antiga API v3 não tinha IDs, então, se alguém mudasse o nome de usuário ou do repositório, era difícil rastrear quem era quem
    Por isso eu implementei meu próprio sistema de gestão de propriedade por equipes
    O provider do Terraform não era grande coisa, então em processos de offboarding aconteciam com frequência problemas como “a única pessoa administradora saiu”
    Todo repositório pertence a uma equipe, e permissões de acesso só são concedidas no nível da equipe

    • Pensar em termos de “não se dá acesso a um usuário, e sim a uma equipe, e o usuário faz parte dessa equipe” é muito mais eficiente
      Esse tipo de controle de acesso baseado em equipes é útil não só no GitHub, mas em outros sistemas também
  • Este é um caso clássico da Lei de Hyrum — quando as pessoas começam a depender de comportamentos não documentados, eventualmente tudo quebra

  • Em modelagem de banco de dados, normalmente se oferece externamente uma chave natural opaca e, internamente, usa-se um ID inteiro incremental

    • Há dois motivos para isso
      1. não expor quantos objetos existem
      2. impedir que alguém simplesmente incremente IDs para percorrer todos os objetos
        Mas IDs compostos reduzem esse problema.
        Por exemplo, se o ID do repositório estiver incluído no ID do objeto, incrementar o ID só permitiria explorar objetos dentro do mesmo repositório
        Se você ainda misturar entropia ou timestamp, o abuso se torna quase impossível
    • Mas chaves naturais podem mudar
      Por isso é mais seguro expor uma chave substituta (surrogate key) sem significado
      Por exemplo, o YouTube pode usar internamente um número de índice, mas externamente fornece um ID em forma de código sem significado
  • Agora faz sentido por que a equipe do GitHub expandiu tanto o suporte a sharded/multi-database no Rails nos últimos anos