fix(memory-save): self-healing git auth bridge + non-silent push hook #31

Closed
opened 2026-08-08 20:51:51 +03:00 by slaid098 · 0 comments
Owner

Контекст

При миграции с GitHub на Forgejo (ADR-093) было удалено insteadOf-правило для GitHub (url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf https://github.com/), но аналогичное правило для Forgejo не создали. Результат: git push на https://git.slaid098.dev/... падает с 401 — нет моста между env-переменными (FORGEJO_TOKEN/FORGEJO_URL) и git-конфигом. git не читает env-переменные, ему нужен токен в ~/.gitconfig или remote URL.

Это касается всех репозиториев, не только opencode-memory. Но проявилось на opencode-memory, потому что memory-save.ts обновляет remote через OPENCODE_MEMORY_REMOTE env var, а post-commit hook тихо глотал ошибки push (2>/dev/null || true). 29 коммитов накопилось локально за сутки, пока пользователь не заметил расхождение.

Дополнительно: ~/.gitconfig не персистится при пересоздании контейнера (если /root volume будет стёрт — insteadOf-правило пропадёт, и проблема вернётся).

Задача

Реализовать self-healing git auth bridge: автоматическое (пере)создание insteadOf-правила для Forgejo из env-переменных, чтобы авторизация git никогда не требовала ручной настройки. Покрывает ВСЕ репо глобально, переживает пересоздание контейнера / стирание ~/.gitconfig.

Вторая часть: сделать post-commit hook памяти не-silent на ошибки push — чтобы при сломанном sync ошибка была видна сразу, а не через неделю.

Контракты

Self-healing auth bridge

  • Источник: FORGEJO_TOKEN (env) + FORGEJO_URL (env, e.g. https://git.slaid098.dev)
  • Мост: git config --global url."https://slaid098:${FORGEJO_TOKEN}@<host>/".insteadOf <FORGEJO_URL>/ — где <host> = хост из FORGEJO_URL (parse), FORGEJO_URL без trailing slash
  • Where: в autoSetup() функции memory-save.ts (строки 18-68) — добавить блок, обеспечивающий insteadOf-правило, рядом с блоком if (remote) (строки 45-61). Вызывается безусловно при каждом memory-save (как уже делает remote-update).
  • Idempotent: проверять существующее правило (git config --global --get-regexp), не переписывать если уже корректно (аналогично логике remote-update на строках 56-59)
  • Guard: если FORGEJO_TOKEN или FORGEJO_URL не заданы — пропустить (не падать, не предупреждать — это валидный кейс для пользователей без Forgejo)
  • Mask: в steps.push(...) НЕ выводить токен (замаскировать как ***)

Non-silent push hook

  • Текущий hook (memory-save.ts:7): #!/bin/bash\ngit push origin master 2>/dev/null || true
  • Новый: hook должен выводить ошибки push в stderr (убрать 2>/dev/null), но не падать (оставить || true — commit не должен фейлиться из-за push)
  • Формат: #!/bin/bash\ngit push origin master || echo "[memory] push failed (non-fatal)" >&2
  • Migration: при следующем memory-save detect старый hook (сравнение с HOOK_BODY на строке 49) и перезаписать новым (needHook=true → writeFileSync)

Инварианты

  1. memory-save НЕ падает, если FORGEJO_TOKEN/FORGEJO_URL отсутствуют — мост пропускается (backward compatible)
  2. memory-save остаётся идемпотентным: повторный вызов не переписывает корректное insteadOf-правило
  3. Post-commit hook НЕ должен делать commit провальным — push error = warning, не fatal
  4. Токен НИКОГДА не попадает в вывод memory-save (steps array) — только замаскированный
  5. Existing репо памяти с GitHub remote → при следующем memory-save remote обновляется (если OPENCODE_MEMORY_REMOTE указывает на Forgejo) + мост создаётся → push работает
  6. Покрывает ВСЕ репо в контейнере (insteadOf глобальный), не только opencode-memory

Граничные случаи

  • FORGEJO_URL с trailing slash (https://git.slaid098.dev/) → вместоOf должен использовать без trailing slash, но матчить с trailing slash (git требует точный match)
  • FORGEJO_TOKEN содержит спецсимволы (например /, +) → git config URL handling может сломаться. Решение: URL-encode токен? Проверить.
  • ~/.gitconfig read-only (immutable) → git config --global упадёт. memory-save должен сообщить warning, не падать.
  • Существующее insteadOf-правило с ДРУГИМ токеном (ротация) → нужно перезаписать (текущая логика remote-update на строках 57-59 перезаписывает при несовпадении — аналог для insteadOf)
  • FORGEJO_URL = https://git.slaid098.dev (без path) vs https://git.slaid098.dev/subpath (Forgejo в subpath) → вместоOf должен покрывать хост целиком, не path
  • Несколько вместоOf-правил (GitHub + Forgejo одновременно) → не конфликтуют, git суммирует

Влияние на связанные компоненты

  • Все git-операции в контейнере: insteadOf глобальный → влияет на ВСЕ git push/git fetch/git clone к git.slaid098.dev. Это желаемое поведение (пользователь явно подтвердил: "для всех репозиториев автоматически").
  • pipeline tools (create-pr, merge-pr, etc.): push feature-веток на Forgejo — сейчас работает через токен-in-URL или curl; после моста — будет работать через чистый URL + insteadOf. Не ломается.
  • memory-doctor.ts: добавить проверку insteadOf-правила в checkGit() (строки ~70-80) — отображать ✓/✗ для "Forgejo auth bridge (insteadOf)" рядом с OPENCODE_MEMORY_REMOTE check (строки 90-92).
  • AGENTS.md: раздел ## Git Host — можно обновить, указав что insteadOf-мост создаётся автоматически memory-save (раньше было "для операций без tool'а — curl к Forgejo API" — теперь git-native push тоже работает).
  • .env.example: OPENCODE_MEMORY_REMOTE комментарий "optional — auto-setup on first memory-save if unset" — можно дополнить: "auth bridge auto-created from FORGEJO_TOKEN/FORGEJO_URL".

Вне scope

  • Создание .env файла на хосте (это ручная операция пользователя, вне контейнера)
  • Миграция других репо с GitHub remote на Forgejo (у них remote уже обновлён вручную при миграции 2026-08-06)
  • Настройка git credential.helper store как альтернатива insteadOf (insteadOf проще и уже работает)
  • Backfill коммитов (29 уже запушены срочным фиксом)
  • setup-memory.sh — удалён в PR#103/ADR-045, не воскрешать

Критерии приемки

  1. memory-save вызывается в окружении с FORGEJO_TOKEN+FORGEJO_URL → создаёт insteadOf-правило в ~/.gitconfig (если отсутствует или токен сменился)
  2. memory-save вызывается БЕЗ FORGEJO_TOKEN/FORGEJO_URL → мост пропускается, не падает, не warns
  3. После memory-save git push на https://git.slaid098.dev/... (чистый URL) проходит без 401
  4. Post-commit hook памяти: при ошибке push выводит [memory] push failed (non-fatal) в stderr, commit остаётся успешным
  5. Существующий hook (старый формат 2>/dev/null || true) перезаписывается новым при следующем memory-save
  6. Токен не попадает в вывод memory-save (steps array) — только ***
  7. memory-doctor отображает статус insteadOf-моста (✓/✗)
  8. Тесты покрывают: мост создаётся / мост идемпотентен / мост пропускается без env / hook не-silent / hook migration
## Контекст При миграции с GitHub на Forgejo (ADR-093) было удалено insteadOf-правило для GitHub (`url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf https://github.com/`), но **аналогичное правило для Forgejo не создали**. Результат: `git push` на `https://git.slaid098.dev/...` падает с 401 — нет моста между env-переменными (`FORGEJO_TOKEN`/`FORGEJO_URL`) и git-конфигом. git не читает env-переменные, ему нужен токен в `~/.gitconfig` или remote URL. Это касается **всех** репозиториев, не только opencode-memory. Но проявилось на opencode-memory, потому что `memory-save.ts` обновляет remote через `OPENCODE_MEMORY_REMOTE` env var, а post-commit hook тихо глотал ошибки push (`2>/dev/null || true`). 29 коммитов накопилось локально за сутки, пока пользователь не заметил расхождение. Дополнительно: `~/.gitconfig` не персистится при пересоздании контейнера (если `/root` volume будет стёрт — insteadOf-правило пропадёт, и проблема вернётся). ## Задача Реализовать **self-healing git auth bridge**: автоматическое (пере)создание insteadOf-правила для Forgejo из env-переменных, чтобы авторизация git никогда не требовала ручной настройки. Покрывает ВСЕ репо глобально, переживает пересоздание контейнера / стирание `~/.gitconfig`. Вторая часть: сделать post-commit hook памяти **не-silent** на ошибки push — чтобы при сломанном sync ошибка была видна сразу, а не через неделю. ## Контракты ### Self-healing auth bridge - Источник: `FORGEJO_TOKEN` (env) + `FORGEJO_URL` (env, e.g. `https://git.slaid098.dev`) - Мост: `git config --global url."https://slaid098:${FORGEJO_TOKEN}@<host>/".insteadOf <FORGEJO_URL>/` — где `<host>` = хост из `FORGEJO_URL` (parse), `FORGEJO_URL` без trailing slash - **Where**: в `autoSetup()` функции `memory-save.ts` (строки 18-68) — добавить блок, обеспечивающий insteadOf-правило, рядом с блоком `if (remote)` (строки 45-61). Вызывается безусловно при каждом `memory-save` (как уже делает remote-update). - **Idempotent**: проверять существующее правило (`git config --global --get-regexp`), не переписывать если уже корректно (аналогично логике remote-update на строках 56-59) - **Guard**: если `FORGEJO_TOKEN` или `FORGEJO_URL` не заданы — пропустить (не падать, не предупреждать — это валидный кейс для пользователей без Forgejo) - **Mask**: в `steps.push(...)` НЕ выводить токен (замаскировать как `***`) ### Non-silent push hook - Текущий hook (memory-save.ts:7): `#!/bin/bash\ngit push origin master 2>/dev/null || true` - Новый: hook должен выводить ошибки push в stderr (убрать `2>/dev/null`), но **не падать** (оставить `|| true` — commit не должен фейлиться из-за push) - Формат: `#!/bin/bash\ngit push origin master || echo "[memory] push failed (non-fatal)" >&2` - **Migration**: при следующем `memory-save` detect старый hook (сравнение с `HOOK_BODY` на строке 49) и перезаписать новым (needHook=true → writeFileSync) ## Инварианты 1. `memory-save` НЕ падает, если `FORGEJO_TOKEN`/`FORGEJO_URL` отсутствуют — мост пропускается (backward compatible) 2. `memory-save` остаётся идемпотентным: повторный вызов не переписывает корректное insteadOf-правило 3. Post-commit hook НЕ должен делать commit провальным — push error = warning, не fatal 4. Токен НИКОГДА не попадает в вывод `memory-save` (steps array) — только замаскированный 5. Existing репо памяти с GitHub remote → при следующем `memory-save` remote обновляется (если `OPENCODE_MEMORY_REMOTE` указывает на Forgejo) + мост создаётся → push работает 6. Покрывает ВСЕ репо в контейнере (insteadOf глобальный), не только opencode-memory ## Граничные случаи - `FORGEJO_URL` с trailing slash (`https://git.slaid098.dev/`) → вместоOf должен использовать без trailing slash, но матчить с trailing slash (git требует точный match) - `FORGEJO_TOKEN` содержит спецсимволы (например `/`, `+`) → git config URL handling может сломаться. Решение: URL-encode токен? Проверить. - `~/.gitconfig` read-only (immutable) → `git config --global` упадёт. `memory-save` должен сообщить warning, не падать. - Существующее insteadOf-правило с ДРУГИМ токеном (ротация) → нужно перезаписать (текущая логика remote-update на строках 57-59 перезаписывает при несовпадении — аналог для insteadOf) - `FORGEJO_URL` = `https://git.slaid098.dev` (без path) vs `https://git.slaid098.dev/subpath` (Forgejo в subpath) → вместоOf должен покрывать хост целиком, не path - Несколько вместоOf-правил (GitHub + Forgejo одновременно) → не конфликтуют, git суммирует ## Влияние на связанные компоненты - **Все git-операции в контейнере**: insteadOf глобальный → влияет на ВСЕ `git push`/`git fetch`/`git clone` к `git.slaid098.dev`. Это желаемое поведение (пользователь явно подтвердил: "для всех репозиториев автоматически"). - **pipeline tools** (`create-pr`, `merge-pr`, etc.): push feature-веток на Forgejo — сейчас работает через токен-in-URL или `curl`; после моста — будет работать через чистый URL + insteadOf. Не ломается. - **memory-doctor.ts**: добавить проверку insteadOf-правила в `checkGit()` (строки ~70-80) — отображать ✓/✗ для "Forgejo auth bridge (insteadOf)" рядом с `OPENCODE_MEMORY_REMOTE` check (строки 90-92). - **AGENTS.md**: раздел `## Git Host` — можно обновить, указав что insteadOf-мост создаётся автоматически `memory-save` (раньше было "для операций без tool'а — curl к Forgejo API" — теперь git-native push тоже работает). - **.env.example**: `OPENCODE_MEMORY_REMOTE` комментарий "optional — auto-setup on first memory-save if unset" — можно дополнить: "auth bridge auto-created from FORGEJO_TOKEN/FORGEJO_URL". ## Вне scope - Создание `.env` файла на хосте (это ручная операция пользователя, вне контейнера) - Миграция других репо с GitHub remote на Forgejo (у них remote уже обновлён вручную при миграции 2026-08-06) - Настройка `git credential.helper store` как альтернатива insteadOf (insteadOf проще и уже работает) - Backfill коммитов (29 уже запушены срочным фиксом) - `setup-memory.sh` — удалён в PR#103/ADR-045, не воскрешать ## Критерии приемки 1. `memory-save` вызывается в окружении с `FORGEJO_TOKEN`+`FORGEJO_URL` → создаёт insteadOf-правило в `~/.gitconfig` (если отсутствует или токен сменился) 2. `memory-save` вызывается БЕЗ `FORGEJO_TOKEN`/`FORGEJO_URL` → мост пропускается, не падает, не warns 3. После `memory-save` `git push` на `https://git.slaid098.dev/...` (чистый URL) проходит без 401 4. Post-commit hook памяти: при ошибке push выводит `[memory] push failed (non-fatal)` в stderr, commit остаётся успешным 5. Существующий hook (старый формат `2>/dev/null || true`) перезаписывается новым при следующем `memory-save` 6. Токен не попадает в вывод `memory-save` (steps array) — только `***` 7. `memory-doctor` отображает статус insteadOf-моста (✓/✗) 8. Тесты покрывают: мост создаётся / мост идемпотентен / мост пропускается без env / hook не-silent / hook migration
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config#31
No description provided.