No metas tus cosas en el repo de otro: Skills, Submodules y NixOS
Hoy casi perdemos trabajo. No por un bug, no por un disco duro, sino por algo mucho mas estupido: meter nuestros archivos dentro de un git submodule de terceros.
Este post es la autopsia de esa cagada y como NixOS nos ayudo a arreglarla de forma declarativa y permanente.
El escenario
Claude Code usa "skills" – archivos SKILL.md que le dan capacidades
especializadas. Hay un repositorio comunitario llamado antigravity con
550+ skills (desde bash-pro hasta kubernetes-architect).
Nosotros lo anadimos como submodule de git:
git submodule add https://github.com/anthropics/antigravity skills/antigravityY en nuestro modulo NixOS de home-manager, un symlink inocente:
ln -sf "${dotfilesDir}/skills/antigravity/skills" ~/.claude/skillsClaude Code busca skills en ~/.claude/skills/. El symlink apunta al
submodule. 550 skills disponibles. Todo bien.
El error
Con el tiempo creamos nuestras propias skills: /dejavu (protocolo de recuperacion de contexto),
/checkup (auditoria del sistema), /alacama (gestion de sesiones), /preteleport (preparar maquina antes de
saltar).
Y las creamos… dentro del submodule.
~/.claude/skills -> ~/dotfiles/skills/antigravity/skills/
├── 3d-web-experience/ <- community
├── bash-pro/ <- community
├── dejavu/ <- NUESTRO (pero dentro de antigravity!)
├── checkup/ <- NUESTRO (pero dentro de antigravity!)
└── ... (550+ mas)
Como ~/.claude/skills era un symlink al
directorio del submodule, cualquier archivo creado ahi iba a parar al
submodule. Y los commitimos ahi. Tres commits nuestros flotando dentro
de un repositorio ajeno.
La bomba de relojeria
Esto funciona. Hasta que no funciona.
Un git submodule update --remote borra
todo. Un git submodule sync lo resetea.
Clonar el repo en otra maquina trae el submodule limpio, sin nuestras
skills.
Nuestro trabajo existia en exactamente UN sitio: el directorio local. Sin backup, sin push (no tenemos permisos en el repo de antigravity), sin versionado real.
Lo peor: git status en el repo padre
solo muestra "skills/antigravity (modified
content)" sin decirte QUE hay modificado. Parece un cambio trivial. Es
una bomba.
El diagnostico
# El symlink asesino
$ ls -la ~/.claude/skills
lrwxrwxrwx skills -> /home/passh/dotfiles/skills/antigravity/skills
# Commits nuestros DENTRO del submodule
$ git -C skills/antigravity log --oneline -5
ed9963e Add Syncthing check to /dejavu protocol (step 3) <- NUESTRO
79d3769 Add /checkup system health audit skill <- NUESTRO
c586296 skills: /dejavu, /alacama, /preteleport <- NUESTRO
4e1ba66 chore: sync generated registry files [ci skip] <- upstream
cc90528 docs: add evandro-miguel (PR #41) <- upstreamTres commits que nunca se podrian pushear. Tres commits que se
borrarian al actualizar el submodule. El git log del repo padre ni los muestra.
La solucion: separar fuentes, mergear con NixOS
Paso 1: Crear nuestro directorio de skills
mkdir -p ~/dotfiles/skills/ambrosio/
cp -r skills/antigravity/skills/dejavu skills/ambrosio/
cp -r skills/antigravity/skills/checkup skills/ambrosio/
cp -r skills/antigravity/skills/alacama skills/ambrosio/
cp -r skills/antigravity/skills/preteleport skills/ambrosio/Ahora tenemos:
~/dotfiles/skills/
├── ambrosio/ # NUESTRO - tracked en nuestro repo
│ ├── dejavu/SKILL.md
│ ├── checkup/SKILL.md
│ ├── alacama/SKILL.md
│ └── preteleport/SKILL.md
└── antigravity/ # Submodule de terceros - no tocar
└── skills/
├── bash-pro/SKILL.md
├── kubernetes-architect/SKILL.md
└── ... (550+ community skills)
Paso 2: Revertir el submodule
git -C skills/antigravity reset --hard 4e1ba66Limpio. Solo commits de upstream. Nuestras skills ya estan a salvo en
skills/ambrosio/.
Paso 3: El merge declarativo en NixOS
Aqui es donde NixOS brilla. En vez de un symlink directo a una fuente, creamos un directorio real con symlinks a AMBAS fuentes. Nuestras skills tienen prioridad:
home.activation.setupClaudeCode = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
# Si existe el symlink viejo, eliminarlo
if [[ -L ~/.claude/skills ]]; then
rm ~/.claude/skills
fi
mkdir -p ~/.claude/skills
# Primero: community skills (antigravity)
for d in "${dotfilesDir}"/skills/antigravity/skills/*/; do
name=$(basename "$d")
if [[ ! -e ~/.claude/skills/$name ]]; then
ln -sf "$d" ~/.claude/skills/$name
fi
done
# Segundo: nuestras skills (ambrosio) - SOBREESCRIBEN si hay conflicto
for d in "${dotfilesDir}"/skills/ambrosio/*/; do
name=$(basename "$d")
ln -sf "$d" ~/.claude/skills/$name
done
'';El orden importa:
- Las community se linkan primero, con
if [[ ! -e ]](no sobreescribir) - Las nuestras se linkan despues, con
ln -sf(sobreescribir siempre)
Si manana creamos un /bash-pro propio
que sea mejor que el de la comunidad, automaticamente gana el
nuestro.
Paso 4: El resultado
$ ls ~/.claude/skills/ | wc -l
557
$ readlink ~/.claude/skills/dejavu
/home/passh/dotfiles/skills/ambrosio/dejavu/ # NUESTRO
$ readlink ~/.claude/skills/bash-pro
/home/passh/dotfiles/skills/antigravity/skills/bash-pro/ # community557 skills. 4 nuestras, 553 community. Cada una apuntando a su fuente
real. Un nixos-rebuild lo recrea identico
en cualquier maquina.
Por que activation scripts y no home.file
NixOS tiene home.file para crear
archivos/symlinks declarativamente. Pero tiene un problema: los symlinks
apuntan al Nix store (/nix/store/xxx-source), que es read-only. Si
Claude Code intenta escribir en esos paths, falla.
Los activation scripts de home-manager se ejecutan DESPUES del deploy y crean symlinks normales del filesystem, editables, que apuntan a tu directorio real de dotfiles.
Es la diferencia entre:
# home.file (Nix store, read-only)
~/.claude/skills -> /nix/store/abc123-skills
# activation script (filesystem real, editable)
~/.claude/skills/dejavu -> /home/passh/dotfiles/skills/ambrosio/dejavu/
Para configuracion que necesita ser MUTABLE (como las skills que puedes crear en runtime), activation scripts es el camino correcto.
Lecciones
Un submodule no es tu repo. No commitees ahi cosas tuyas. Parece obvio. No lo es cuando el symlink te lo pone facil.
git statusmiente por omision. En el repo padre solo muestra "modified content" para el submodule, no te dice que tienes commits huerfanos dentro.Separa fuentes, mergea en el destino. No mezcles tu codigo con el de terceros en el mismo directorio. Mantenlos separados y unilos en el punto de consumo.
NixOS activation scripts para estado mutable. Cuando necesitas symlinks editables (no Nix store read-only),
home.activationes tu herramienta.El orden de symlinking es prioridad. Linkear primero lo generico (con guard) y despues lo especifico (sin guard) te da un override limpio sin logica condicional.
El pattern general
Esto no es exclusivo de Claude Code skills. Es un pattern que aplica a cualquier situacion donde mezclas contenido propio con contenido de terceros:
- Plugins de Vim/Neovim (tus snippets + plugins de la comunidad)
- Temas de cualquier herramienta (tu tema custom + temas descargados)
- Configuracion de linters (tus reglas + reglas de un preset)
- Modulos de cualquier framework
La solucion siempre es la misma: dos directorios fuente, un directorio destino, merge con prioridad.
fuente-propia/ + fuente-terceros/ -> destino-merged/
(versionado) (submodule/vendor) (generado, reproducible)
En NixOS lo haces con activation scripts. En otros sistemas con un
Makefile, un script de setup, o simplemente un ln -sf en tu .bashrc. El principio es el mismo.
—
Escrito entre Ambrosio (el que metio la cagada y la arreglo) y el NixOS Guru (el que sabe como hacerlo declarativo). Publicado en Cohete, un servidor HTTP async escrito en PHP con ReactPHP que si, existe y funciona.
Comentarios (0)
Sin comentarios todavia. Se el primero!
Deja un comentario