--- title: sync-vault-lib.sh — pipefail + grep -q молча пропускал коммиты (492 файла застряли) type: log namespace: family tags: [vault, sync, git, sync-script, pipefail, bug, fixed] date: 2026-09-26 --- # `git status --porcelain | grep -q .` под `set -o pipefail` = коммиты молча не создаются **Когда:** 2026-09-26 09:53 UTC, найдено и починено в cron-тике Taiga. **Статус:** **фикс применён** (`/opt/data/sync-vault-lib.sh`), синк проверен. ## Симптом Каждый тик печатал `Pushed ✓` ×3 и `Already up to date.`, exit 0, три SHA равны — но рабочий каталог `/obsidian-syncthing` **не очищался**, и в нём накопилось **492 изменения**, включая реальную правку пользователя: - 491 файл — смена режима `100644 → 100755` (класс «mode-flip», см. `2026-09-26-mode-flip-644-755.md`), включая папки `personal/` 199, `family/` 166, `work/` 108; - `personal/plans/music-tags-review/1-needs-decision.md` — 4 чекбокса `- [ ]` → `- [x]` (пользователь отмечал разобранные треки, вероятно с телефона). То есть правки телефона **не доезжали до bare**, пока в клоне было мало изменений — коммит делался; как только изменений становилось много, коммит перестал создаваться вообще. ## Причина В `/opt/data/sync-vault-lib.sh`: ```bash set -euo pipefail ... if git status --porcelain | grep -q .; then # ← вот это git add -A 2>/dev/null || true ... git commit ... || true else true # ← «нечего коммитить», молча fi ``` `grep -q` выходит сразу после первого совпадения. При большом выводе `git status` (492 записи ≈ **23 КБ**) он продолжает писать в уже закрытый pipe, получает `SIGPIPE`, и **`pipefail` объявляет весь pipeline упавшим (exit 141)**. `if` уходит в `else` — ветка коммита не выполняется вообще. Ошибка полностью безмолвная: stderr у `add`/`commit` погашен, `Pushed ✓` печатается как ни в чём не бывало. Порог — размер вывода: у коммита из 2 файлов (09:44:37) условие было истинным, у 492 файлов — ложным. **Доказательство (воспроизведение):** ```bash cd /obsidian-syncthing bash -c 'set -euo pipefail; if git status --porcelain | grep -q .; then echo IF=TRUE; else echo IF=FALSE; fi' # → IF=FALSE (492 записи) bash -c 'set -eu; if git status --porcelain | grep -q .; then echo IF=TRUE; else echo IF=FALSE; fi' # → IF=TRUE (то же состояние, без pipefail) ``` ## Фикс ```bash STATUS="$(git status --porcelain 2>/dev/null || true)" # подстановка не даёт SIGPIPE if [ -n "$STATUS" ]; then git add -A 2>/dev/null || true MSG="$(printf '%s\n' "$STATUS" | awk 'NF{n++; if(n<=25) printf "%s ", $2} END{if(n>25) printf "(... +%d more)", n-25}' || true)" git commit -m "[$STAMP] $LABEL: $MSG" 2>/dev/null || true fi ``` Две правки в одном месте: 1. **условие** через `$( )` вместо `| grep -q` — нет пайпа, нет SIGPIPE; 2. **сообщение коммита** собирается одним `awk` (читает вход целиком) — раньше при массовом flip в сообщение и в `/tmp/vault-sync.log` попадали все ~500 путей; теперь первые 25 + `(... +N more)`. ⚠️ Не заменять на `awk ... | head -25 | tr ...`: `head` рвёт пайп, `awk` получает SIGPIPE, подстановка падает под `set -e` — тик завершается `exit 141` (проверено, первая версия фикса так и падала). ## Проверка после фикса ``` синк: exit 0, Pushed ✓ ×3 bare = /vault = /obsidian-syncthing = 041885fa git status --porcelain пуст в обоих клонах, маркеров конфликта 0 Push failed 0, Fetch failed 4 (все исторические, тик 09:40 до origin-guard) safe.directory 3 записи (без дублей), insteadOf 2 (без дублей) коммит 12a7566e: 492 файла, сообщение "... (+467 more)" чекбоксы [x] в bare HEAD и в /vault = 8 → правка телефона доехала ``` ## Связанное - `2026-09-26-syncthing-clone-origin-url-flip.md` — flip `origin` в клоне (guard уже вшит) - `2026-09-26-syncthing-clone-root-objects-acl-confusion.md` — root-объекты и ACL (разбор другой сессии, решения за Alex): там же симптом «документ не доехал до телефона» - `2026-09-26-mode-flip-644-755.md` — откуда берутся `100644 → 100755` - `family/how-to/vault-git-sync.md`, skill `vault-sync-infrastructure`