opencode-config/docs/decisions/023-pr-55-parse-remote-url.md
Sergey e413a6c132
fix(pipeline-status): parse_remote_url breaks with git insteadOf userinfo (#55)
* fix(pipeline-status): handle git insteadOf userinfo in parse_remote_url

* test(pipeline-status): add tests for URL with userinfo

* docs(handoff): set PR number 55 in handoff + ADR-023

---------

Co-authored-by: opencode-agent <agent@slaid098.dev>
2026-07-24 21:47:39 +03:00

3.6 KiB
Raw Blame History

ADR-023: parse_remote_url strips git insteadOf userinfo from HTTPS URLs

Статус

Accepted (2026-07-24)

Контекст

PR#53 (ADR-022) добавил git config --global url."https://x-access-token:${GITHUB_TOKEN}@github.com/".insteadOf https://github.com/ в setup-memory.sh для non-interactive HTTPS auth в Docker. После этого git remote get-url origin возвращает rewritten URL с встроенным userinfo:

https://x-access-token:github_pat_...@github.com/slaid098/opencode-config.git

parse_remote_urlpipeline-status.py и spec-status.py) использовал regex https?://([^/]+)/([^/]+)/(.+?)(?:\.git)?$ — группа [^/]+ жадно матчит всё между :// и первым /, включая user:password@ (символ @ не исключён). В результате host = x-access-token:github_pat_...@github.com вместо github.com.

Это ломало get_memory_file_path() → путь repos/x-access-token:...@github.com/slaid098/opencode-config.md (не существует) → check_memory возвращал NOT_DONE для каждого PR после PR#53. get_repo_full_name() случайно не ломался (возвращает org/repo, host отбрасывается), но фикс применён в обоих файлах для консистентности.

CI на PR#53 не поймал regression — тесты test_parse_remote_url_* покрывали только plain URLs без userinfo.

Решение

Изменить HTTPS-regex в parse_remote_url (оба файла) на:

re.match(r"https?://(?:[^/@]*@)?([^/]+)/([^/]+)/(.+?)(?:\.git)?$", url)

(?:[^/@]*@)? — опциональная non-capturing группа, пропускающая user:password@ userinfo перед host:

  • Для plain https://github.com/... группа не матчится (нет @) → host = github.com.
  • Для https://user:token@github.com/... группа матчит user:token@ и отбрасывается → host = github.com.
  • [^/@] внутри userinfo не матчит ни / ни @, поэтому корректно останавливается на первом @ (поддерживает user:pass, user, token@ формы).

7 новых тестов: 4 в test_pipeline_status.py (parse + get_memory_file_path), 3 в test_spec_status.py (parse + get_repo_full_name).

Альтернативы

  1. Использовать git config --get remote.origin.url вместо git remote get-url origin — bypasses insteadOf, возвращает raw URL. Отклонено: coupling с git internals, ломается если кто-то задаст remote URL с token напрямую (не через insteadOf).
  2. Strip userinfo через url.split("@")[-1] — хрупкий: ломается если path содержит @ (legitimate в GitHub URLs для определённых refs/paths).
  3. Рефакторинг parse_remote_url в shared moduleоба файла дублируют функцию. Отклонено для этого PR: риск для ADR-010 cwd-aware _resolve_repo_root logic, выходит за рамки bugfix.
  4. Не чинить, отключить insteadOf — отклонено: insteadOf нужен для non-interactive memory clone (PR#53/ADR-022), regression в pipeline_status — меньшее из зол.

Regex fix (выбран) — минимальный, root-cause, без env coupling, покрывает оба caller'а.