SEBASTIANDEV_
  • Sobre mí
  • Proyectos
  • Blog
arrow_backVolver al blog
2026 SEBASTIÁN. TERMINAL_ACTIVA
terminalmail
AboutContactPrivacy
06_OCT_2026schedule5 min de lectura

cortex-sync 0.7.0: doctor, dry-run, y dos bugs que encontré haciendo esto

#CLAUDE-CODE#CLI#DX
cortex-sync 0.7.0: doctor, dry-run, y dos bugs que encontré haciendo esto

npm install -g cortex-sync@latest

Las últimas tres versiones fueron seguridad y escalabilidad. Esta es la fase que el roadmap tenía marcada como "DX y confiabilidad" — nada urgente, solo pulir cosas del día a día: un comando de diagnóstico, un modo de solo-lectura para sync, reintentos cuando la API de GitHub falla, y un par de cosas menores que quedaron anotadas hace rato.


cortex doctor

Hasta ahora, si algo estaba mal configurado, te enterabas corriendo cortex sync y viendo qué error tiraba. cortex doctor corre los mismos chequeos sin tocar nada:

 $ cortex doctor \ \
 ✓ config: Config found at ~/.cortex/config.json \ \
 ✓ config-permissions: Config file mode is 600 \ \
 ✓ storage: local is reachable \ \
 ✓ passphrase: CORTEX_PASSPHRASE is set \ \
 ✓ tools: All configured tools are present 

All checks passed. 

Revisa que el archivo de config exista y tenga permisos 600 (guarda el token), que el backend configurado sea alcanzable (para GitHub: valida el token contra /user y después prueba el repo; para local: que la carpeta destino exista), si CORTEX_PASSPHRASE está seteada, y si las herramientas configuradas (claude-code, etc.) siguen presentes en esta máquina.

cortex sync --dry-run

Imprime el diff — qué se agregaría, qué cambió, qué se borraría — sin cifrar, subir, ni tocar el manifiesto local. Una excepción honesta: si es el primer contacto con un proyecto que nunca se sincronizó, igual crea el archivo de salt del proyecto (sin cifrar, idempotente) — lo necesito de todos modos para poder descifrar un manifiesto remoto existente y calcular el diff bien. Es el único write que un dry-run puede llegar a hacer, y lo reutiliza el próximo sync real.

Reintentos con backoff en la API de GitHub

Un 403 por rate limit, o un 503 pasajero de GitHub, tiraba abajo el comando entero — había que volver a correrlo a mano. Ahora fetchWithRetry reintenta con backoff exponencial (300ms, 600ms, 1200ms) en 429, 5xx, y el 403 de rate limit específicamente:

 function isRetryableStatus(res: Response): boolean { \ \
 if (res.status === 429) return true; \ \
 if (res.status >= 500) return true; \ \
 // GitHub reusa 403 tanto para rate limit como para error de permisos — \ \
 // solo el de rate limit vale la pena reintentar. \ \
 if (res.status === 403 && res.headers.get('x-ratelimit-remaining') === '0') return true; \ \
 return false; \ \
 } 

Lo importante es lo que no reintenta: un 404 o un 401 no van a cambiar de resultado por esperar y volver a intentar — reintentarlos solo hace perder tiempo antes de mostrar el error real.

Dos bugs, encontrados de dos formas distintas

El primero, mirando mi propio código: mcp.ts reportaba version: '0.1.0' fijo, sin importar qué versión estuviera realmente publicada — un resto de cuando escribí el server por primera vez. cli.ts ya leía la versión real desde package.json; le copié el mismo patrón a mcp.ts, y me llevé una sorpresa al verificarlo: la ruta relativa correcta no es la misma en ambos archivos.

cli.ts vive en src/cli.ts (un nivel bajo la raíz) y usa require('../package.json'). mcp.ts vive en src/commands/mcp.ts (dos niveles), así que por profundidad de archivo le correspondería require('../../package.json') — y esa fue mi primera versión. Falló. tsup empaqueta todo en un solo dist/cli.js, así que en tiempo de ejecución la ruta se resuelve relativa a ese archivo (un nivel bajo la raíz), no a donde vivía mcp.ts en el código fuente. Tuve que probarlo contra el bundle real para encontrar la ruta que efectivamente funciona — la misma que ya usaba cli.ts, por casualidad de que ambos quedan a la misma profundidad después de empaquetar.

El segundo, ya estaba anotado en el roadmap: el script GIT_ASKPASS que uso en Windows escribía el token PAT directo en el cuerpo de un .cmd, sin escapar nada. %, &, |, <, >, ^, " son todos caracteres especiales para cmd.exe — la rama de Unix ya escapaba comillas simples correctamente, la de Windows no escapaba nada. Nunca se explotó ni se reportó; lo arreglé porque estaba en la lista, no porque algo hubiera fallado. La solución es más simple que enumerar caracteres a escapar: el token ahora viaja por una variable de entorno (CORTEX_ASKPASS_TOKEN) en vez de escribirse en el archivo — así el contenido del script es siempre el mismo texto fijo, sin importar qué tenga el token.

ESLint, por fin

No había linter configurado. Lo agregué (typescript-eslint, flat config) y lo metí en el CI, antes de typecheck/test/build. La corrida encontró cuatro cosas reales al aplicarlo por primera vez: un catch que descartaba el error original en vez de encadenarlo con cause, un import sin usar en un test, y el script de postinstall (que es CommonJS puro, corre antes del build) necesitando su propia configuración de globals — nada grave, pero es exactamente el tipo de cosas que un linter agarra antes de que alguien lo note a mano.


Actualizate

 npm install -g cortex-sync@latest # → 0.7.0 

211 tests en 35 archivos, todos en verde, más una corrida real del binario compilado (cortex doctor contra una config real, cortex sync --dry-run confirmando que no sube nada).

GitHub: SebastiaWeb/cortex-cli