- 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:
MDEwOlJlcG9zaXRvcnkyMzI1Mjk4 → 010: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
1 comentários
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 aodatabaseIdda API RESTMas é melhor não depender dessa implementação interna e consultar diretamente o campo
databaseIdda API GraphQLDocumentaçã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 campodatabaseId, então o certo é usar issoA 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
permalink,urle a interfaceUniformResourceLocatable, então não há necessidade de montar URLs manualmenteÉ por isso que a API fornece permalink. IDs e padrões de link podem mudar a qualquer momento
Esse tipo de abordagem também é muito usado em tokens de paginação
IDs como
010:Repository2325298têm uma estrutura clara010é um enum de tipo,Repositoryé o nome e2325298é o ID no bancoOu seja, é um formato de prefixo de comprimento (length prefix). Repository tem 10 caracteres, Tree tem 4
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
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
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
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
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