Skip to content

Lesson 08 · 24 in the route · 9 min read · Level 2

The kit on the Mac

Repos by git bundle, port, tests, layered config.

The story behind this lesson Bringing my Claude Code setup to the Mac: bash 3.2, bash 5 and a sync that overwrote my rules

Mi configuración de Claude Code vive en un repositorio propio, orquestador: reglas, hooks, una barra de estado y un script que lo instala en ~/.claude. Lo escribí en Windows. Que funcione en la Mac no se puede suponer; hay que probarlo. Los detalles están en el paso orquestador de la guía.

Medir antes de portar

Antes de adaptar nada, corrí verify.sh en la Mac tal como estaba, para guardar qué se rompe. Resultado: 6 FALLA y 34 avisos de DERIVA. Las fallas eran las esperadas: la barra no estaba instalada, Antigravity no está en la Mac, y el historial de sesiones vive en Windows. Esa línea base es la justificación del port y, después, su prueba: mismo comando, antes y después.

Dos bash

macOS trae bash 3.2, de 2007; Apple no lo actualizó por el cambio de licencia a GPLv3. Homebrew instala bash 5. Mi barra de estado usa funciones que exigen bash 4.2 o superior, así que arranca con un guard: si detecta un bash viejo, se vuelve a ejecutar a sí misma con el nuevo.

Para probar el guard de verdad hay que arrancar con el bash viejo. Por eso corrí las dos suites de pruebas con los dos intérpretes. Dentro de tmux, para que un corte de SSH no matara el trabajo:

  • /bin/bash: 49 OK en la suite de la barra y 38 OK en la de hooks.
  • bash de Homebrew: los mismos 49 y 38.

Cuatro corridas, cuatro en verde. Y el guard se ejercitó: no hubo SKIP.

Instalar dos veces

sync-to-home.sh copia el kit a ~/.claude. La primera corrida dio 34 added, 0 updated, 1 merged, 0 unchanged. La segunda, 0 added, 0 updated, 0 merged, 35 unchanged. Eso es idempotencia: correr el script dos veces deja el mismo estado que correrlo una. Es lo que me deja ejecutarlo sin miedo.

Dos tropiezos:

  • ./sync-to-home.sh dio permission denied. Windows no guarda el bit de ejecución, y git los registró como 100644. En la Mac hay que llamarlos con bash archivo.sh. El arreglo de fondo se hace en el repo del kit, y sigue sin parchear.
  • kit-update.sh empieza con git pull, que por SSH falla porque las credenciales de la Mac están en el llavero. Como la copia ya estaba al día, corrí sus pasos a mano.

Un número falso

Con el idioma del sistema en español, printf espera coma decimal y rechaza 12.3456. La barra mostraba $12,00 en vez de $12.35. Un número falso es peor que ninguno. El arreglo propuesto es fijar LC_NUMERIC=C; quedó reportado, no aplicado.

Cuánto cuesta dibujar la barra

Mismo método que en Windows: 20 llamadas por 3 repeticiones, con un JSON de ejemplo.

RutaWindows + Git BashMac M2 Pro
Piso (bash + una llamada a jq)~225 ms~30 ms
Render completo~476 ms~45 ms

La explicación que tengo: en Windows, Git Bash emula fork y crear un proceso cuesta unos 50 ms; en macOS, fork es nativo. Arrancar desde /bin/bash y que se re-ejecute añade solo 2 a 4 ms.

Configuración en capas

Hay otro episodio, y ocurrió en la máquina Windows. La primera vez que corrí kit-update.sh ahí, pisó tres reglas que vivían solo en mi ~/.claude/CLAUDE.md en vivo. El script respalda antes de copiar, y por eso las recuperé del respaldo. Pero el aprendizaje es de diseño:

  • Un archivo que a la vez edito a mano y que un script sobrescribe va a perder cambios.
  • Las reglas de seguridad (no matar procesos que no lancé, no borrar recursivamente fuera del repo) pasaron al CLAUDE.md del repo, que viaja con el kit.
  • Mis reglas personales de trabajo autónomo pasaron a un archivo propio, rules/personal.md, que el script de construcción del kit público borra y verifica que no aparezca. Quedó con una comprobación que falla si esa regla se cuela en el kit.

Resultado: lo que es de todos va en una capa, lo que es mío va en otra, y ningún sync toca la segunda. Antes de sincronizar hay que comprobar el sentido de una DERIVA: si el archivo vivo es más nuevo que el repo, copiar en la dirección equivocada borra trabajo.

Qué falta

  • Los hooks llaman a jq sin ruta. Si Claude Code se abre desde el Dock, ¿ve /opt/homebrew/bin en su PATH? Si no, los hooks no hacen nada en silencio. Es una prueba que requiere interfaz gráfica y sigue pendiente.
  • El script que construye el kit público usa sed -i de GNU; en macOS fallará hasta portarlo.

Siguiente: la primera tarea que mandé de una máquina a otra.

Goal

Get the orquestador and enjambre repos into ~/Documents/Portfolio/ on the Mac, measure what breaks before adapting the kit to macOS, install it into ~/.claude, and check that it passes its tests under both of the Mac’s bash versions. And understand why your personal configuration does not travel with the public one.

Why

The Claude Code configuration kit (orquestador: status line, hooks, rules, skills) was written on Windows. Before porting it, it pays to measure what breaks on the Mac as it stands: that list is the justification for the port and, afterwards, the proof that it worked (same command, before and after). And because the kit installs on top of ~/.claude, which also holds things of yours, you have to decide which part is public and shareable and which part is personal (section 4).

Steps

1. Cloning on the Mac without credentials: git bundle

The normal route would be gh repo clone, but gh over SSH did not work on the Mac (see 09-agente-desatendido.md). Instead of copying a token from one machine to another, the repository was moved, not the credential:

# En Windows: empaquetar la rama remota (no la local: así no viaja trabajo sin pushear)
PS> git bundle create orq.bundle origin/master
PS> scp orq.bundle mac:/tmp/orq.bundle
# En la Mac (por SSH): repo nuevo + importar la rama + apuntar el remoto a GitHub
mac$ git init -b master ~/Documents/Portfolio/orquestador
mac$ cd ~/Documents/Portfolio/orquestador
mac$ git fetch --update-head-ok /tmp/orq.bundle refs/remotes/origin/master:refs/heads/master
mac$ git reset --hard master
mac$ git remote add origin https://github.com/IchiSieben/orquestador.git

Concept: a bundle is a git repository packed into a single file. You can clone or fetch from it as if it were a remote. It is useful for passing history between machines with no network route to GitHub or, as here, with no credentials on the destination.

2. Baseline: verify.sh on the Mac, changing nothing

PS> ssh mac 'cd ~/Documents/Portfolio/orquestador && bash verify.sh'

(capture of the real setup: not published; see docs/CAPTURAS.md for the demo-mode ones)

Result (2026-10-04): 6 FALLA, 34 DERIVA warnings, the rest OK.

FailureWhy it fails on the Mac (expected)
statusline.sh / settings.jsonThe status line is not yet installed in the Mac’s ~/.claude
xreview calls agyAntigravity is not installed on the Mac (phase 5)
sessions.json ≥ 41 sessionsThe session history lives on Windows
agy --version / OAuth credentialAntigravity is neither installed nor logged in

The 34 DERIVA lines all say the same thing in different words: the kit’s files are in the repo but not in the Mac’s ~/.claude. That is what the installation will fix after the port; it was deliberately not run yet.

3. After the port: tests, installation, measurement

3.1 The port, one line per change. (Done on Windows, before testing on the Mac.) A guard that requires bash ≥ 4.2 and re-executes itself under Homebrew’s bash if it starts under the factory one; stat with BSD or GNU format depending on $OSTYPE; jq also looked up in /opt/homebrew/bin; @mac/@win/@linux on the first line of the status line. This is the diff of the stat line (from stat -c '%Y' to a per-system format, STAT_FMT), with the status line at the bottom:

(capture of the real setup: not published; see docs/CAPTURAS.md for the demo-mode ones)

3.2 The tests, under the factory bash and under Homebrew’s. macOS ships bash 3.2 (from 2007; Apple does not update it because of the GPLv3 license). Homebrew installs bash 5. The kit requires bash ≥ 4.2 and, if it starts under the old one, it re-executes itself under the new one (the guard). That is why the tests are run with both: running with /bin/bash exercises the guard.

mac$ /bin/bash tests/statusline_test.sh              → 49 OK, 0 fallas
mac$ /bin/bash tests/hooks_test.sh                   → 38 OK, 0 fallas
mac$ /opt/homebrew/bin/bash tests/statusline_test.sh → 49 OK, 0 fallas
mac$ /opt/homebrew/bin/bash tests/hooks_test.sh      → 38 OK, 0 fallas

They were run inside tmux (tmux new -d -s phaseb "…; tmux wait-for -S phaseb-done" and then tmux wait-for phaseb-done): if the SSH connection drops, the job keeps going.

3.3 Installing into ~/.claude, twice (the second run must change nothing). First, a backup (~/.claude → ~/.claude.pre-kit-20261004; the script also makes its own).

mac$ bash sync-to-home.sh   → 34 added, 0 updated, 1 merged, 0 unchanged
mac$ bash sync-to-home.sh   → 0 added, 0 updated, 0 merged, 35 unchanged

Concept — idempotence: running it twice gives the same result as running it once. That is what lets you re-run an installer without fear.

verify.sh afterwards: no drift, and only the 3 checks that belong to another machine remain red (Antigravity and the Windows sessions file):

(capture of the real setup: not published; see docs/CAPTURAS.md for the demo-mode ones)

3.4 The status line on the Mac.

(capture of the real setup: not published; see docs/CAPTURAS.md for the demo-mode ones)

(The branch symbol shows up as a square: freeze ignores the chosen font; in the real terminal it looks fine.)

3.5 How much does it cost to draw the status line? Same method as on Windows: 20 calls × 3 repetitions, with a sample JSON.

PathWindows + Git BashMac M2 Pro
Floor (bash startup + one jq call, no git)~225 ms~30 ms
Full render (git, HANDOFF, dirty files)~476 ms~45 ms

Why such a difference: on Windows, creating a process costs ~50 ms and a bash fork ~28 ms (Git Bash emulates fork on top of Windows). On macOS, fork is native and costs very little. Starting from /bin/bash (which re-executes into bash 5) adds only ~2–4 ms.

4. Layered config: public and personal

The kit has a public version (claude-kit, an open repo) and it installs on top of ~/.claude, which also holds personal rules. The rule that came out of a real stumble (below): nothing personal lives only in a file that a script is going to overwrite, and nothing personal travels in the public kit.

LayerWhereLanguageTravels in the public kit
General safety rules (do not touch processes you did not launch; recursive delete only inside the repo or a scratch dir, with its four commands)the repo’s CLAUDE.mdEnglishyes
Personal rules (the owner’s “autonomous mode”)home/.claude/rules/personal.mdSpanishnever

How the second point is guaranteed: build-kit.sh deletes any copy of personal.md from the kit and fails if the text MODO AUTÓNOMO appears anywhere in the kit (the check was tested in both directions on a test kit). For personal.md to be loaded no @import is needed: this was verified with a fresh claude -p session, with no tools and outside any repo, which quoted point 5 of the rule and named the file.

On the Mac, kit-update.sh also brings in personal.md (it is in the mirror, not in the kit) and Claude Code has to be restarted afterwards. The orquestador’s handoff anticipates this; it was not verified on the Mac.

What actually went wrong

1. git clone -b master orq.bundle → “Remote branch master not found”. When origin/master is packed, the reference inside the bundle is refs/remotes/origin/master, not refs/heads/master. Fix: an explicit fetch of that reference.

2. git fetch … :refs/heads/master → “refusing to fetch into branch checked out”. Git protects the active branch; --update-head-ok tells it we know what we are doing (the repo was empty).

3. ./sync-to-home.sh → permission denied. The scripts were committed from Windows, which does not store the execute bit (100644 in git). On the Mac they are not executable. Immediate fix: bash archivo.sh. Real fix (in the kit’s repo): git update-index --chmod=+x.

4. kit-update.sh fails on the Mac. It starts with git pull, which fails over SSH because the credentials are in the locked keychain (see 09-agente-desatendido.md). Since the copy was already up to date (d100eb4, equal to origin/master), its steps were run by hand.

5. With the system in Spanish, the status line showed $12,00 instead of $12.35. With LC_ALL=es_ES.UTF-8, printf expects a decimal comma, rejects 12.3456 (“invalid number”) and the status line prints a false number, worse than none. Reported to the kit; the planned fix is to set LC_NUMERIC=C at the top of statusline.sh, plus a test with a Spanish locale.

6. Installing the kit overwrote personal rules (on Windows). When running kit-update.sh on Windows to resolve 8 files with DERIVA, the first run overwrote three rules that lived only in the live ~/.claude/CLAUDE.md: the two safety rules and autonomous mode. They were safe because the script backs up before copying (~/.claude.bak-20261004-052034). The session noticed and restored the live version from that backup, keeping the sync’s version aside:

(capture of the real setup: not published; see docs/CAPTURAS.md for the demo-mode ones)

Afterwards the owner decided on the split in section 4 (orquestador commit 1247193). The line cap on CLAUDE.md went from 70 to 72: a safety rule is not trimmed to make it fit.

How to verify

PS> ssh mac 'cd ~/Documents/Portfolio/orquestador && bash verify.sh'

It must finish with no DERIVA and with only three failures, all belonging to another machine (sessions.json, agy --version, Antigravity’s OAuth credential). And the four tests from section 3.2 green. Finally, two runs of sync-to-home.sh in a row: the second must say 0 added, 0 updated, 0 merged and everything unchanged.

Self-check

  1. Why do you pack origin/master and not master with git bundle?
    AnswerSo that unpushed local work does not travel; the bundle contains only what is already on the remote.
  2. What does running the tests with /bin/bash prove, in addition to Homebrew’s bash?
    AnswerThe re-execution *guard*: with the factory bash 3.2, the script must relaunch itself under bash ≥ 4.2.
  3. Why does personal.md not travel in the public kit, and how is that prevented by accident?
    AnswerBecause it contains personal rules; `build-kit.sh` deletes any copy and fails if the text `MODO AUTÓNOMO` appears in the kit.
Saved only in this browser.