git submodule para coding agents: gitlink 160000, no un clone extra
Resumen
git submodule inspecciona y actualiza un repo montado. El superproyecto guarda un gitlink 160000, no el árbol. status: - no init, + SHA distinto, U conflicto. update --init clona al SHA del índice en detached HEAD. Un agente no hace add, deinit --all, --remote ni foreach a ciegas. Git 2.50.1.

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 submodule no es un segundo clone. El man (git-submodule(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-submodule HTTP 200, last-modified 2026-08-31; man Git 2.54.0, 2026-04-19) inspecciona, inicializa o actualiza un repositorio montado. gitsubmodules(7) (HTTP 200, last-modified 2026-08-31): el superproyecto guarda un gitlink (modo 160000) con el SHA que espera, más una entrada en .gitmodules. El working tree del submódulo vive aparte; su .git suele ser un archivo que apunta a $GIT_DIR/modules/<name>/.
Sin argumentos, git submodule es status. No clona, no commitea, no mueve HEAD del superproyecto.
Esta guía no sustituye clone (--recurse-submodules sin pathspec inicializa todos) ni rm (quitar el gitlink de verdad). El contrato: qué forma corre un agente, qué prefijo de status significa, y por qué no rellena vendor/ con archivos.
Tres letras, un path
| Prefijo | Significado (man status) | Qué hace el agente |
|---|---|---|
| (espacio) | checkout = SHA del índice | nada |
- | no inicializado | update --init -- <path> |
+ | checkout ≠ SHA del índice | no “arreglar” con --remote |
U | conflicto de merge en el submódulo | parar |
--cached: imprime el SHA del superproyecto, no el HEAD del submódulo. --recursive: entra a submódulos anidados.
Verificado 2026-09-04 (Git 2.50.1 / Apple Git-155): add stagea 160000 <SHA> 0 <path> (git ls-files -s). Tras el commit, git ls-tree HEAD <path> = 160000 commit <SHA>. Tras deinit <path>, status imprime - + el SHA del gitlink. deinit sin pathspec → Use '--all' if you really want to deinitialize all submodules, 128.

Lo que el agente sí / no corre
| Quiero | Comando | Trampa |
|---|---|---|
| ¿Hay submódulos? | git submodule status | ls vendor/ y “falta el código” |
| Checkout al SHA del índice | git submodule update --init -- <path> | --recurse-submodules en clone |
| SHA registrado | git submodule status --cached | git log dentro de vendor/ |
| Quitar el checkout local | git submodule deinit -- <path> | deinit --all / rm -rf vendor |
| Quitar el gitlink del repo | rm del path | deinit “para borrar el submódulo” |
Prohibido en autónomo:
add. Stagea gitlink +.gitmodulesy clona. Verificado: path ya en el índice → already exists in the index, 128.--force(man): bypasea ignore y nombres duplicados (child→child1). Un agente no añade dependencias al árbol.deinitsin pathspec o--all. El man: sin pathspec erra a propósito. Verificado: 128.--allcon working tree sucio → contains local modifications; use '-f', 128.-ftira cambios locales.update --remote. El man: usa el remote-tracking branch, no el SHA del gitlink; fetchea antes. Verificado: un commit extra en origin → checkout nuevo y status+(el índice sigue el SHA viejo). Eso sucia el superproyecto.update --force. El man (checkout):git checkout --forceen el submódulo; descarta cambios locales aunque el SHA ya coincida.update --rebase/--merge. HEAD del submódulo no queda detached; un conflicto lo deja a medias. Default = checkout detached.foreachcon un comando inventado. El man: shell en cada checkout;$name,$sm_path,$sha1,$toplevel. No-cero corta. Verificado:foreach 'false'→ run_command returned non-zero, 128.foreach 'false || :'→ 0 y sigue. Un agente no evalúa shell a ciegas.set-url/set-branch. Mutan.gitmodules/ config ysyncel remote. No es lectura.absorbgitdirs. El man: recursivo por default. Mueve el.gitdel submódulo a$GIT_DIR/modules/.- Copiar
submodule.<name>.update=!comandoa.gitmodules.gitmodules(5):!commandno está permitido ahí (seguridad).inittampoco copia un update custom de.gitmodulesa.git/config. - Rellenar el directorio vacío del gitlink con archivos o con otro clone. El índice espera un commit, no un tree.
update --init sin pathspec respeta submodule.active si está configurado; si no, inicializa todos. Un agente nombra el path.
Receta (60 segundos)
Solo en un worktree propio:
git status -sb
git submodule status
git submodule update --init -- vendor/child
git submodule status
Si status enseña -, faltaba init. Si enseña espacio, el checkout ya es el SHA del índice: no toques. Si enseña +, el working tree del submódulo no es el gitlink: no “alinees” con --remote ni commitees el gitlink salvo que el ticket lo pida.
Tras update --init, el submódulo queda en detached HEAD en el SHA registrado. Verificado: rev-parse --abbrev-ref → HEAD. No es una rama para push.

Superproyecto vs submódulo
gitsubmodules(7): dos usos (historia independiente, o partir un repo grande). En ambos el superproyecto fija una versión. Traer “lo último de main” es otro commit del gitlink, no un pull dentro de vendor/.
gitmodules(5) (HTTP 200, last-modified 2026-08-31): keys obligatorias path y url. update permitido en el archivo: checkout, rebase, merge, none — no !command. ignore=all|dirty|untracked|none cambia qué muestran status y diff; no cambia el gitlink.
Quitar un submódulo del historial no es deinit. El man de deinit: If you really want to remove a submodule from the repository and commit that use git-rm(1). deinit solo desregistra checkout local y la sección en .git/config.
Checklist
- Worktree propio.
git status -sb. Primerogit submodule status. -
-→update --init -- <path>. Espacio → nada.+/U→ parar. - Cero
add, cerodeinit --all, cero-f, cero--remote, ceroforeachautónomo. - Cero rellenar el path del gitlink con archivos.
- Borrar el submódulo del repo es rm. Clonar el superproyecto es clone.
FAQ
¿ls vendor/child vacío significa que faltan archivos? No. Tras deinit el directorio se limpia; el gitlink sigue en el índice. update --init -- vendor/child restaura el checkout.
¿--recurse-submodules en clone alcanza? El man de clone: sin pathspec inicializa todos (submodule.active=.). Equivale a update --init --recursive al terminar. Nombra el path o no recursa.
¿Puedo commitear dentro del submódulo? Es otro repo. Un commit ahí no mueve el gitlink del superproyecto hasta un add del path. Un agente one-shot no cruza esa frontera.
El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. submodule no es clone: monta un commit fijo; no crea otro objeto store del superproyecto.
Lecturas relacionadas
Sigue explorando Coding Agents y otras piezas para builders.

git show-ref para coding agents: refs locales, no el dump al LLM

git for-each-ref para coding agents: refs locales, cero update-ref

git hash-object para coding agents: SHA del contenido, no un commit
