From cb894780164fabb3f2080a830ac7f721d7e265cf Mon Sep 17 00:00:00 2001 From: Nic O'Connor Date: Mon, 13 Jul 2026 13:29:07 -0400 Subject: [PATCH] mcp-config: write the sync hook to ~/.zshrc.local, not ~/.zshrc ~/.zshrc is MANAGED: zsh/install.sh overwrites it wholesale (cp) on every bootstrap run. install.sh appended the mcp-sync hook to that same file, so the next bootstrap silently deleted it -- sync.sh then never ran, so OPENCODE_CONFIG was never exported and opencode came up with no models and no skills. It looked like a broken gateway or a bad model; it was a wiped shell hook. ~/.zshrc.local is the unmanaged file the managed zshrc sources last, so the hook now survives a re-bootstrap. Verified: hook lands in ~/.zshrc.local (0600), managed ~/.zshrc is left untouched, and OPENCODE_CONFIG still exports after simulating the bootstrap overwrite. Also warn instead of failing silently when python3 is missing: sync.sh then deploys the base config verbatim with an EMPTY models map, which presents as "the gateway has no models" rather than "a dependency is absent". Co-Authored-By: Claude Opus 4.8 (1M context) --- mcp-config/install.sh | 13 ++++++++++--- mcp-config/sync.sh | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mcp-config/install.sh b/mcp-config/install.sh index 8e5d2f4..678cd9e 100644 --- a/mcp-config/install.sh +++ b/mcp-config/install.sh @@ -53,13 +53,20 @@ chmod 0600 "$KEYFILE" say "Key stored at $KEYFILE (0600)." # --- 3. wire the shell rc -------------------------------------------------- -# Detect the user's interactive rc; default to zsh on macOS, bash elsewhere. +# For zsh the hook goes in ~/.zshrc.local, NOT ~/.zshrc. The dotfiles repo +# MANAGES ~/.zshrc and overwrites it wholesale on every bootstrap +# (zsh/install.sh), so a hook written there survives only until the next +# bootstrap run — and then models and skills silently vanish, because sync.sh +# never runs and OPENCODE_CONFIG is never exported. ~/.zshrc.local is the +# unmanaged file the managed zshrc sources last, so the hook persists. case "${SHELL:-}" in - *zsh) RC="$HOME/.zshrc" ;; + *zsh) RC="$HOME/.zshrc.local" ;; *bash) RC="$HOME/.bashrc" ;; - *) [ -f "$HOME/.zshrc" ] && RC="$HOME/.zshrc" || RC="$HOME/.bashrc" ;; + *) [ -f "$HOME/.zshrc" ] && RC="$HOME/.zshrc.local" || RC="$HOME/.bashrc" ;; esac touch "$RC" +# It sits alongside the user's secrets; keep it non-readable by others. +[ "$RC" = "$HOME/.zshrc.local" ] && chmod 0600 "$RC" 2>/dev/null || true if grep -qF "$HOOK_MARKER" "$RC" 2>/dev/null; then say "Shell hook already present in $RC." diff --git a/mcp-config/sync.sh b/mcp-config/sync.sh index 9b90741..b842bd5 100644 --- a/mcp-config/sync.sh +++ b/mcp-config/sync.sh @@ -140,8 +140,14 @@ with os.fdopen(fd, "w") as f: os.replace(tmp, dest) PYEOF elif [ -n "$_base" ]; then + # No python3: the base config is deployed verbatim, so its "models" map + # stays EMPTY and opencode shows the provider with zero models. That looks + # like a broken gateway rather than a missing dependency, so say so — this + # is the one case worth breaking the silence for. mv "$_base" "$MCP_SYNC_DEST" 2>/dev/null || true _base="" + printf 'mcp-sync: python3 not found — models cannot be discovered.\n' >&2 + printf 'mcp-sync: install python3 (bootstrap.sh does), then open a new shell.\n' >&2 fi [ -n "$_base" ] && rm -f "$_base" }