Por qué cambié de Bun a Deno para las Skills de Claude Code
La semana pasada escribí sobre usar npx bun para escribir skills de Claude Code con dependencias de terceros. El enfoque funcionaba para casos simples—scripts autocontenidos con paquetes que se autoinstalan, sin paso de compilación. Pero después de usarlo en entornos reales, descubrí un problema significativo.
La autoinstalación de Bun solo funciona cuando no existe un directorio node_modules en el directorio de trabajo o en cualquier directorio padre. Cuando node_modules está presente en cualquier lugar superior del árbol, Bun cambia a la resolución de módulos estándar de Node.js. Los especificadores de versión en las importaciones—la característica principal que hacía útil el enfoque—lanzan errores VersionSpecifierNotAllowedHere:
$ cd ~/my-project # tiene node_modules/$ cat skill.ts#!/usr/bin/env -S npx -y bunimport chalk from "chalk@^5.0.0"console.log(chalk.green("Hello"))$ ./skill.tserror: VersionSpecifierNotAllowedHereimport chalk from "chalk@^5.0.0"^
Esto se rompe en escenarios prácticos. ¿Ejecutar una skill desde dentro de un directorio de proyecto? Roto. ¿Trabajar en un monorepo donde algún ancestro tiene node_modules? Roto. ¿Tu directorio home casualmente tiene un node_modules antiguo de un experimento olvidado? Roto.
Para skills de Claude Code portables que podrían ejecutarse desde cualquier lugar, esto es una trampa. El script funciona cuando lo pruebas en ~/.claude/skills/, luego falla misteriosamente cuando Claude lo invoca desde un directorio diferente. El mensaje de error oscurece el problema—diagnosticarlo requiere entender la lógica de resolución interna de Bun.
El crédito por la solución es para J Edward Wynia, quien me señaló hacia Deno en respuesta a ese artículo. Olvidé por qué me salté Deno inicialmente—probablemente porque la sintaxis de Bun parecía más limpia—pero la sugerencia era correcta.
Por qué Deno Resuelve Esto
El especificador npm: de Deno funciona independientemente de si existe node_modules. Las dependencias siempre van a la caché global de Deno en ~/.cache/deno. Los directorios node_modules locales no afectan la resolución. Comportamiento consistente en todas partes.
El mismo truco de distribución con npx funciona. Al igual que npx -y bun, puedes usar npx -y deno para ejecutar Deno sin instalarlo globalmente. Cualquier entorno con npm puede ejecutar scripts de Deno.
Una advertencia: si Deno ya está instalado en tu sistema, npx -y deno todavía descarga una copia separada a la caché de npm (~40MB, comparable al coste de primera descarga de ~100MB de Bun). Para sistemas con Deno preinstalado, usa deno run directamente. El enfoque npx apunta a la portabilidad—scripts que funcionan en cualquier máquina con npm, independientemente de lo que esté preinstalado.
El Enfoque Deno
Así es como se ve una skill basada en Deno:
#!/usr/bin/env -S npx -y deno run --allow-read --allow-writeimport { parse } from "npm:csv-parse@^5.0/sync"import chalk from "npm:chalk@^5.0.0"import { z } from "npm:zod@^3.23"const inputPath = Deno.args[0]const content = await Deno.readTextFile(inputPath)const rows = parse(content, { columns: true })console.log(chalk.green(`Parsed ${rows.length} rows`))
El prefijo npm: es más verboso que las importaciones directas de Bun, pero clarifica el origen de los paquetes. TypeScript funciona nativamente. El fijado de versiones vive en la ruta de importación, igual que con Bun. No se requiere deno.json ni mapa de importación—las dependencias se resuelven directamente desde los especificadores.
Deno requiere banderas de permisos—--allow-read, --allow-write, --allow-net, etc. Más verboso que Bun, pero declaras exactamente lo que hace el script. Para skills que se ejecutan a través de Claude Code, los permisos explícitos documentan a qué puede acceder el script. Para entornos de confianza, --allow-all (o -A) se salta la ceremonia.
Compromisos
| Aspecto | Bun | Deno |
|---|---|---|
| Sintaxis de importación | import x from "[email protected]" | import x from "npm:[email protected]" |
| Seguro con node_modules | No | Sí |
| Rendimiento bruto | ~20-30% más rápido | Ligeramente más lento |
| TypeScript | Nativo | Nativo |
| Modelo de permisos | Permisivo por defecto | Requiere banderas explícitas |
Bun es más rápido. Tiempo de inicio, rendimiento en ejecución, servicio HTTP—Bun supera consistentemente a Deno en los benchmarks. Si estás construyendo una API de producción o una herramienta CLI crítica para el rendimiento, eso importa.
Para las skills de Claude Code, no importa.
Por Qué el Rendimiento No Importa Aquí
El tiempo de pensamiento del agente empequeñece el tiempo de ejecución del script. Claude tarda de dos a cinco segundos en decidir qué hacer a continuación. Una skill que se ejecuta en 50 milisegundos frente a 80 milisegundos es efectivamente lo mismo—ambos son instantáneos comparados con el bucle de decisión del agente.
La fiabilidad importa más. Una skill que funciona desde cualquier directorio es más valiosa que una skill que es un 30% más rápida pero se rompe en monorepos.
Ejemplo Práctico para Skills
La estructura sigue el mismo patrón del artículo original—un SKILL.md apuntando a scripts ejecutables. Los únicos cambios son el shebang y las APIs específicas de Deno:
#!/usr/bin/env -S npx -y deno run --allow-read --allow-writeimport { parse } from "npm:csv-parse@^5.0/sync"import * as XLSX from "npm:xlsx@^0.20"const inputPath = Deno.args[0]const content = await Deno.readTextFile(inputPath)const rows = parse(content, { columns: true })console.log(JSON.stringify(rows, null, 2))
Claude ejecuta la skill, el script accede a los paquetes npm, y todo funciona independientemente del directorio.
Conclusión
El prefijo npm: es más verboso. Las banderas de permisos añaden ceremonia. La sintaxis de importación de Bun es más limpia y rápida. Pero la fiabilidad de Deno a través de diferentes estructuras de directorios lo convierte en la mejor opción para las skills de Claude Code.
No tienes que depurar por qué una skill funciona en un directorio y falla en otro. No tienes que documentar "esto solo funciona fuera de proyectos con node_modules". El script simplemente funciona.
Si Bun añade una bandera para forzar la autoinstalación independientemente de la presencia de node_modules, lo reconsideraria. Hasta entonces, la consistencia de Deno gana.
Referencias
- Escribiendo Potentes Skills de Claude Code con npx bun — La exploración original de este enfoque
- Deno — Un runtime moderno para JavaScript y TypeScript
- Compatibilidad npm de Deno — Cómo funciona el especificador
npm: - Documentación de Autoinstalación de Bun — Entendiendo cuándo se activa la autoinstalación
- Documentación de Skills de Claude Code