Guía9 min

git pack-refs para coding agents: packed-refs, no el dump al LLM

Resumen

git pack-refs junta tags y refs quietas en $GIT_DIR/packed-refs para no pagar un inode por ref. Default no toca branches activas. --auto sí; --all solo si el humano lo pide. Cero --no-prune autónomo. Cero editar packed-refs. Cero cat al contexto. Distinto de gc y de maintenance. Git 2.50.1.

GitHub
Muchas refs sueltas se compactan en un solo packed-refs; HEAD y el working tree no se mueven

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

git pack-refs empaqueta heads y tags para un acceso más barato al repositorio. El man (git-pack-refs(1); git-scm.com/docs/git-pack-refs; man local Git 2.50.1, 2025-07-22): Pack heads and tags for efficient repository access. SYNOPSIS: git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>].

Tradicionalmente cada tip vive en un archivo bajo $GIT_DIR/refs. Con cientos o miles de tags eso gasta inodes y duele al lookup. El comando escribe esas refs en un archivo, $GIT_DIR/packed-refs. Si una ref no está en el árbol refs/, Git la busca ahí.

No es gc. gc comprime objetos, expira reflogs y también puede llamar a pack-refs (gc.packRefs, default true). pack-refs solo mueve refs. Tampoco es maintenance: maintenance orquesta tareas; pack-refs es una de ellas (weekly en la estrategia incremental). Y no es show-ref: show-ref lee; pack-refs reescribe el almacenamiento de refs.

Contrato: git pack-refs --auto. Cero --all autónomo. Cero --no-prune. Cero editar packed-refs. Cero cat .git/packed-refs al LLM. Cero mezclarlo con git gc.

La regla de oro: tags quietas, branches sueltas

El default no empaqueta todas las branches. Empaqueta tags y las refs que ya estaban packed, y deja el resto. El man lo dice: las branches se desarrollan; compactar su tip no ayuda al rendimiento diario. Un update posterior de una branch siempre crea un archivo suelto otra vez bajo $GIT_DIR/refs.

La práctica recomendada del man: pack-refs --all una vez en un repo con demasiadas refs, y después git pack-refs (sin --all) de vez en cuando. Tags no se espera que cambien. Las heads activas se desempacan al tocarse; la siguiente corrida sin --all las deja sueltas.

--all sí empaqueta todas las refs, con tres excepciones: hidden, broken y symbolic. Un agente no lo corre en el clone del humano: compacta main, HEAD simbólico no (bien), pero sí todas las heads históricas. Si el humano pide compactar un mirror con 40k tags, --all es el comando; si el agente “limpia” un working tree de feature, no.

Refs sueltas vs un packed-refs único; las branches activas siguen en archivos

Flags (qué sí, qué no)

FlagQué haceAgente
(default)Tags + refs ya packed; prune de sueltas empaquetadasOK si el humano pide compactar tags
--autoPack según el estado del ref DB; formato files o reftableContrato. Puede no-op
--allTodas las refs excepto hidden/broken/symbolicSolo si el humano lo pide. Cero autónomo
--no-pruneEmpaqueta y deja las sueltasCero. Duplica refs a propósito
--include <glob>Solo esos globs; quita el default de tagsCero autónomo. --exclude gana si hay overlap
--exclude <glob>No empaca esos globs; no desempaqueta packedCero autónomo

--auto no es un alias de --all. En formato files, decide por el ratio de refs sueltas contra el tamaño de packed-refs: cuanto más grande el packed, más sueltas hacen falta para volver a compactar. En reftable, compacta tablas para mantener una secuencia geométrica (N al menos el doble de N+1). El comportamiento “puede cambiar en el futuro”, cita el man. Un agente no asume que --auto reescribió nada: si no hay trabajo, sale.

--include precluye el default de tags. Si el agente pasa --include 'refs/heads/tmp/*' pensando “solo esto”, deja de empacar tags. Con --all, --include es no-op. Symbolic y broken nunca se empacan.

--no-prune deja el archivo suelto y la entrada packed. Git resuelve; el disco no. Un agente no lo usa “por si acaso”.

packed-refs no es un log para el modelo

$GIT_DIR/packed-refs es un archivo de texto con SHA y path. Un dump al contexto es el mismo anti-patrón que volcar show-ref o for-each-ref: miles de tags, SHA de release viejos, y cero señal para la tarea.

Leer una ref: git show-ref --verify --quiet refs/tags/v1.2.3 (exit 0/1) o git for-each-ref --count=20 --format='%(refname)' refs/tags. No cat .git/packed-refs. No ls .git/refs/tags. El man avisa un bug documental viejo: textos que dicen “existe el archivo .git/refs/heads/<branch>” cuando quieren decir “la branch existe”. Packed o suelta, la verdad es la ref, no el inode.

Tampoco edites packed-refs a mano. Un SHA mal pegado o una línea a medias rompe lookup. El porcelain escribe el archivo; el agente no es un editor de refs.

gc.packRefs (man de gc): correr pack-refs deja el repo no clonable por Git anterior a 1.5.1.2 sobre transports tontos (HTTP). Default true. Un agente de 2026 no desactiva eso “por compatibilidad con Git 1.5”. Si el humano tiene un bare servido por dumb HTTP a clientes fósiles, es su problema de ops, no un flag que el agente apague.

Relación con gc y maintenance

git gc llama a pack-refs cuando gc.packRefs no es false. Por eso un gc --auto puede compactar tags sin que nadie haya escrito pack-refs. El agente que “solo quería objetos” ya tocó refs. Contrato de gc: --auto, nunca --aggressive / --prune=now / --force. Si el humano no pidió housekeeping, no corras ni gc ni pack-refs.

git maintenance run --auto --quiet puede incluir la tarea pack-refs (weekly en incremental). Ahí el orquestador decide. Un agente no dispara --task=pack-refs a pelo en el clone ajeno, igual que no dispara --task=gc. Distinto de prune: prune borra objetos sueltos, no refs.

HEAD y el working tree no se mueven. pack-refs no es checkout, no es reset, no es un merge. Si después de correrlo git status cambió, no fue este comando: alguien más tocó el árbol.

packed-refs como tabla compacta; lookup por path, no por inode

Checklist del agente

  1. ¿El humano pidió compactar refs o hay evidencia de miles de tags lentas? Si no, no corras pack-refs.
  2. Comando: git pack-refs --auto. Si no-op, reporta “nada que empacar” y para.
  3. --all solo con pedido explícito (mirror, repo de tags, historial de branches muertas).
  4. Nunca --no-prune, nunca --include/--exclude inventados, nunca editar packed-refs.
  5. Para listar: show-ref / for-each-ref con pathspec y --count. Cero dump, cero cat.
  6. No sustituyas maintenance ni gc con pack-refs “porque es más barato”. Empaqueta refs, no objetos.
  7. Si otro proceso tiene el lock del ref DB, espera o aborta. No --force (pack-refs ni siquiera lo tiene).

FAQ

¿pack-refs borra branches? No. Mueve el almacenamiento del tip. La ref sigue existiendo. --no-prune deja también el archivo suelto; el default sí quita la suelta después de packed.

¿Puedo empacar solo refs/tags/*? Default ya prioriza tags. --include 'refs/tags/*' funciona, pero apaga el default y no empaca refs packed de otro namespace. No lo uses “por precisión” si no sabes el glob.

¿Y reftable? --auto compacta tablas con regla geométrica. No edites las tablas. No asumas packed-refs en un repo reftable: el archivo puede no existir.

¿Lo corro en CI? En un clone efímero, no. En un mirror persistente con 50k tags, un job semanal git pack-refs --auto (o maintenance) sí. Nunca --all en cada PR.

Si estás armando el flujo de un coding agent, el siguiente paso práctico está en el curso de instalar un agente y en el hub de comparativas.