From e00611015ab92eaa73da01a0b3bccdedb202255d Mon Sep 17 00:00:00 2001 From: Dmitry Dementiev Date: Wed, 18 Mar 2026 15:01:34 +0300 Subject: [PATCH] =?UTF-8?q?fix(claude):=20settings.json=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D1=80=D1=83=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=D0=BC?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=20=D1=81=D0=B8=D0=BC=D0=BB=D0=B8=D0=BD?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20stow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - stow tree-folding создавал симлинк ~/.claude → dotfiles/claude/.claude/, из-за чего Claude Code модифицировал settings.json прямо в репозитории, а рантайм-данные (sessions, credentials) попадали в каталог dotfiles. - Что: - claude убран из stow-пакетов, установка через ручную секцию в install.sh. - settings.json копируется в ~/.claude/ (оригинал защищён chmod 444). - добавлена автомиграция: симлинк → директория с переносом рантайм-данных. - грязный settings.json в репо автоматически сбрасывается через git checkout. - добавлен .stow-local-ignore для защиты от случайного stow claude. - check.sh дополнен проверками: ~/.claude — директория, settings.json — копия. - Проверка: - bash install.sh (на машине с симлинком — мигрирует, иначе — идемпотентно). - bash agents/.agents/skills/dotfiles-check/check.sh. Co-Authored-By: Claude Opus 4.6 (1M context) --- agents/.agents/skills/dotfiles-check/check.sh | 29 +++++++++- claude/.stow-local-ignore | 3 + install.sh | 57 ++++++++++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 claude/.stow-local-ignore diff --git a/agents/.agents/skills/dotfiles-check/check.sh b/agents/.agents/skills/dotfiles-check/check.sh index 55cda0b..3131afd 100755 --- a/agents/.agents/skills/dotfiles-check/check.sh +++ b/agents/.agents/skills/dotfiles-check/check.sh @@ -28,6 +28,8 @@ echo "════════════════════════ for pkg_dir in "$DOTFILES_DIR"/*/; do pkg=$(basename "$pkg_dir") [[ "$pkg" == .* ]] && continue + # claude управляется вручную (копирование settings.json), не через stow + [[ "$pkg" == "claude" ]] && continue # Точки входа — элементы первого уровня внутри пакета entries=() @@ -99,7 +101,25 @@ if [ -f "$install_script" ]; then echo "" echo "Доп. проверки (install.sh):" - # chmod 444 на settings.json + # Claude Code: ~/.claude должна быть директорией, не симлинком + if [ -L "$HOME/.claude" ]; then + err "~/.claude — симлинк (должна быть директория). Запустите install.sh" + elif [ -d "$HOME/.claude" ]; then + pass "~/.claude — директория (не симлинк)" + else + err "~/.claude — не найден" + fi + + # Claude Code: settings.json должен быть копией, не симлинком + if [ -L "$HOME/.claude/settings.json" ]; then + err "~/.claude/settings.json — симлинк (должна быть копия)" + elif [ -f "$HOME/.claude/settings.json" ]; then + pass "~/.claude/settings.json — файл (копия)" + else + err "~/.claude/settings.json — не найден" + fi + + # chmod 444 на settings.json в репозитории if [ -f "$DOTFILES_DIR/claude/.claude/settings.json" ]; then perms=$(stat -c %a "$DOTFILES_DIR/claude/.claude/settings.json" 2>/dev/null || true) if [ "$perms" = "444" ]; then @@ -116,6 +136,13 @@ if [ -f "$install_script" ]; then skip "~/.claude/settings.local.json — не найден (создайте через install.sh)" fi + # skills симлинк + if [ -L "$HOME/.claude/skills" ]; then + pass "~/.claude/skills — симлинк" + else + skip "~/.claude/skills — не найден" + fi + # bashrc if grep -q 'bash_custom' "$HOME/.bashrc" 2>/dev/null; then pass "~/.bashrc содержит подключение bash_custom" diff --git a/claude/.stow-local-ignore b/claude/.stow-local-ignore new file mode 100644 index 0000000..a3be5d9 --- /dev/null +++ b/claude/.stow-local-ignore @@ -0,0 +1,3 @@ +# Claude Code управляется вручную через install.sh (копирование, не симлинк). +# Этот файл не даёт stow случайно создать симлинк ~/.claude. +\.claude diff --git a/install.sh b/install.sh index 7f72998..4fd911f 100755 --- a/install.sh +++ b/install.sh @@ -20,7 +20,7 @@ if ! command -v stow &>/dev/null; then fi # --- Stow пакеты --- -PACKAGES=(bash tmux vim zed vscode agents opencode codex claude gemini) +PACKAGES=(bash tmux vim zed vscode agents opencode codex gemini) info "Применяю stow пакеты..." cd "$DOTFILES_DIR" @@ -32,7 +32,53 @@ for pkg in "${PACKAGES[@]}"; do fi done -# --- Claude: settings.local.json --- +# --- Claude Code: установка без stow --- +# settings.json КОПИРУЕТСЯ (не линкуется), чтобы Claude Code не модифицировал +# файл в репозитории. Локальные настройки — в settings.local.json. + +# Миграция: если ~/.claude — stow-симлинк, раскрываем в реальную директорию +if [ -L "$HOME/.claude" ]; then + info "Миграция: ~/.claude — симлинк, преобразую в директорию..." + CLAUDE_STOW_DIR=$(readlink -f "$HOME/.claude") + rm "$HOME/.claude" + mkdir -p "$HOME/.claude" + # Переносим рантайм-данные из stow-директории + for item in "$CLAUDE_STOW_DIR"/* "$CLAUDE_STOW_DIR"/.[!.]*; do + [ -e "$item" ] || continue + base=$(basename "$item") + # Пропускаем git-tracked файлы — они останутся в репозитории + case "$base" in .gitignore|settings.json|skills) continue ;; esac + mv "$item" "$HOME/.claude/" 2>/dev/null || true + done + info "Рантайм-данные перенесены в ~/.claude/" +fi + +mkdir -p "$HOME/.claude" + +# Skills (симлинк) +SKILLS_SRC="$DOTFILES_DIR/agents/.agents/skills" +if [ -d "$SKILLS_SRC" ]; then + ln -sfn "$SKILLS_SRC" "$HOME/.claude/skills" +fi + +# settings.json — копия из репозитория (не симлинк!) +CLAUDE_SETTINGS_SRC="$DOTFILES_DIR/claude/.claude/settings.json" +CLAUDE_SETTINGS_DST="$HOME/.claude/settings.json" +if [ -f "$CLAUDE_SETTINGS_SRC" ]; then + # Если settings.json в репо был изменён (например, Claude Code писал через симлинк) — сбрасываем + if cd "$DOTFILES_DIR" && ! git diff --quiet claude/.claude/settings.json 2>/dev/null; then + warn "claude/.claude/settings.json имеет локальные изменения — сбрасываю" + git checkout claude/.claude/settings.json + fi + # Защищаем оригинал в репозитории + chmod 444 "$CLAUDE_SETTINGS_SRC" + # Копируем в ~/.claude/ (перезаписывает при повторном запуске) + cp -f "$CLAUDE_SETTINGS_SRC" "$CLAUDE_SETTINGS_DST" + chmod 644 "$CLAUDE_SETTINGS_DST" + info "settings.json скопирован (оригинал защищён chmod 444)" +fi + +# settings.local.json — локальные переопределения (не в git) CLAUDE_LOCAL="$HOME/.claude/settings.local.json" if [ ! -f "$CLAUDE_LOCAL" ]; then info "Создаю $CLAUDE_LOCAL (локальные настройки Claude)..." @@ -46,13 +92,6 @@ else warn "$CLAUDE_LOCAL уже существует, пропускаю" fi -# --- Claude: защита settings.json от перезаписи --- -CLAUDE_SETTINGS="$DOTFILES_DIR/claude/.claude/settings.json" -if [ -f "$CLAUDE_SETTINGS" ]; then - chmod 444 "$CLAUDE_SETTINGS" - info "settings.json защищён от записи (chmod 444)" -fi - # --- Bash: подключение в .bashrc --- BASHRC="$HOME/.bashrc" BASHRC_LINE='[ -f ~/.bash_custom ] && source ~/.bash_custom'