2026 : OpenClaw PATH, préfixes Homebrew et échec d'exécution de serveurs MCP sous launchd sur un Mac mini ProxyMac
Les équipes qui exécutent OpenClaw sur des Mac mini M4 loués à Hong Kong, au Japon, en Corée, à Singapour ou aux États-Unis partagent des journaux où les outils MCP affichent env: node: No such file or directory ou uvx: command not found alors que le même binaire réussit dans Terminal.app. Ce texte formalise le contrat de PATH entre shell de connexion et launchd : pourquoi brew --prefix diffère sur Apple Silicon, comment lire une plist LaunchAgent sans deviner, et une échelle de cinq vérifications alignée sur les serveurs MCP, le déploiement et l'installation, plus une matrice, un extrait EnvironmentVariables à valider, des appels vers l'aide et le hub OpenClaw. Documentez hachage et PATH par locataire sur l'hôte partagé, sinon les artefacts CI se mélangent aux mises à jour Homebrew interactives pendant que l'agent silencieux tourne la nuit. Versionnez les chemins d'écriture userspace avant qu'un socle sécurité ne casse les liens ~/.local.
Terminal et launchd : deux univers
Les shells interactifs chargent /etc/zprofile, ~/.zprofile et ~/.zshrc, souvent avec eval "$(/opt/homebrew/bin/brew shellenv)" en tête. Les agents launchd héritent d'un environnement minimal : PATH retombe sur /usr/bin:/bin:/usr/sbin:/sbin tant que la plist n'étend pas. Les passerelles OpenClaw qui lancent des MCP ne voient donc pas npx ni pnpm, tandis que which npx côté Terminal affiche /opt/homebrew/bin/npx. Ce n'est pas « OpenClaw a perdu le MCP » : c'est ENOENT de execve. Les builds headless calibrés en session ssh mais déployés sans EnvironmentVariables s'écartent dès qu'un brew nocturne installe un autre secondaire Node alors que le JSON pointe toujours vers l'ancien shim.
- Prouver l'écart : comparer
printenv PATHen sessionssh mini 'launchctl print gui/…'et en shell de connexion. - Conserver l'exécutable MCP en chemin absolu pendant que le PATH d'aide se stabilise.
- Versionner (voir gitops de config) pour ne pas subir de rotation silencieuse de racine Homebrew.
sudo parallèle ne supprime pas votre plist parce qu'une session VNC a duré plus longtemps.
Matrice de préfixe Homebrew (trois colonnes)
| Génération | Préfixe brew | Symptôme usuel |
|---|---|---|
| Mac mini M4 Apple Silicon | /opt/homebrew | node manquant, /opt/homebrew/bin/node -v v22.x |
| Mac mini Intel hérité | /usr/local | config MCP contient encore /opt/homebrew/bin/uvx d'un clone portable |
| Flotte mixte | deux disques | ordre de shims erroné si /usr/local/bin précède /opt/homebrew/bin |
Ne mélangez pas interpréteurs x86 et arm64 dans la même plist sans y attacher toutes les sondes d'intégrité. Rosette peut brouiller la lecture. Fournissez file sur chaque binaire MCP, comme un journal de compilation : le mini en datacentre n'émet pas le même sifflement que l'ultraportable qui a écrit le JSON de démonstration.
Plist EnvironmentVariables : survivre au reboot
Ajoutez un dictionnaire EnvironmentVariables au LaunchAgent. D'abord Homebrew, ensuite les chemins Apple, enfin p. ex. ~/.local/bin pour uv. Après édition : launchctl bootout gui/$(id -u)/domain plist et kickstart (éviter launchctl unload seul en macOS 14+). Joignez des sondes de supervision pour alerte en 3 minutes dès qu'un PATH s'effiloche. Citez ID de ticket et instantané de retour en arrière avant l'audit de sécurité.
Exemple (valider en préprod)
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
umask des LaunchAgents contre le ticket qui autorisait le staging.
Cinq vérifications avant de réécrire tout le MCP
- Hachage :
shasum -a 256 $(which uvx)côté Terminal, comparer au chemin absolu intégré au JSON. - Environnement launchd :
launchctl print user/$(id -u)/limitet domaine d'agent ; confirmer le PATH chargé. - Non-login :
env -i PATH=/usr/bin:/bin /opt/homebrew/bin/npx --versionprouve que npx survit au minimum. - JSONL : rechercher
ENOENT, lire le diagnostic. - Rollback : congeler la plist signée, selon retour d'upgrade, avant un déploiement PATH massif sur HK / JP / KR / SG / US, canary par région plutôt qu'un redémarrage de tous les gateways à l'heure de pointe.
Node, uv et disposition des shims : pourquoi les gestionnaires de versions éclatent avec launchd
On aime fnm, mise et asdf pour jongler entre dizaines de Node par dépôt, mais ces outils placent le plus souvent leurs ajustements de PATH dans ~/.zshrc, que launchd n’exécute jamais. Lorsqu’un JSON MCP appelle npx @scope/server, c’est d’abord npx qu’il faut trouver en chemin absolu ; seulement ensuite interviennent ~/.npm et le reste. Sur un mini ProxyMac neuf, copier la config d’un portable donne volontiers ce scénario : corepack enable a mis pnpm dans PATH côté shell interactif, tandis que l’agent pointe un shebang #!/usr/bin/env node qui aboutit au binaire système (parfois v18 sur l’image) au lieu de la v22 exigée par le fichier de verrou. L’échec n’apparaît pas proprement comme « binaire manquant » : on récolte ERR_PNPM_UNSUPPORTED_ENGINE perdu au milieu des relances OpenClaw.
uv obéit au même principe : uvx atterrit en général dans ~/.local/bin ou ~/.cargo/bin après le script curl, répertoires visibles au Terminal mais souvent absents du PATH hérité par launchd. Plutôt qu’allonger indéfiniment la pliste, beaucoup d’équipes posent un script enveloppe minimal, par ex. /usr/local/bin/mcp-env.sh (propriétaire root:wheel, pas d’emplacement inscriptible par le monde en tête) qui exporte PATH une fois et exec l’entrée réelle. Les auditeurs préfèrent un fichier relu qu’une ribambelle de bouts de plistes. Tenez le wrapper sous Git avec la même rigueur que les extraits de redémarrage du gateway, launchctl et reprise afin d’isoler mardi de jeudi.
| Runtime | Où l’on installe d’ordinaire | Ce que launchd voit sans correctifs |
|---|---|---|
| Node via Homebrew | /opt/homebrew/bin/node | On ne va souvent qu’à /usr/bin/env ; shim Homebrew manquant → ENOENT |
| Chaînes uv | ~/.local/bin/uvx | Le tilde n’est pas étendu dans une chaîne de pliste si vous ne le faites pas |
| pnpm géré par Corepack | shim à côté de node | Aligné seulement si le même répertoire précède /usr/bin dans PATH |
/opt/homebrew/bin/node -e "console.log(process.version)" et pousse la ligne de sortie standard dans votre canal JSONL ; lorsqu’un brew upgrade remplace le binaire, la chaîne de version bondit avant l’usager.
FAQ
Tout envelopper dans /bin/zsh -lc ? Possible, masque les erreurs et gêne les signaux. Préférez un PATH explicite, sauf besoin de fonctions de connexion.
Rosetta casse-t-elle brew pour MCP ? Lorsqu'un binaire est x86_64 et l'agent attend arm64, croiser file $(which node) et l'architecture cible d'OpenClaw.
Et les blocages stdio ? PATH répare l'exec, les tampons réparent les tuyaux : lire stdio buffer quand l'exécution démarre mais l'échange sature.
Pourquoi le Mac mini ProxyMac fige les contrats de PATH
Un Mac mini M4 dédié cimente /opt/homebrew sur la NVMe à côté d'OpenClaw, sans brew uninstall d'un outillage partagé. arm64 natif évite Rosetta, mémoire unifiée tient des travailleurs MCP en parallèle, cinq régions clonent le même modèle de plist. Quand le PATH se stabilise, montez le parallélisme via le guide d'agents parallèles, réservez des nœuds sur les tarifs, pointez les opérateurs vers l'aide et VNC dès qu'une boîte de dialogue exige l'écran. Couplage recommandé avant qu'un autre SRE, fuseau voisin, ne duplique le cluster sans lire le même screen-wrapper.
Fixer le PATH, déployer OpenClaw partout
HK / JP / KR / SG / US · Apple Silicon M4