Resolve the login pin from the scripts' own location, not CWD #24

Closed
opened 2026-08-10 12:28:42 +00:00 by claude · 0 comments
Collaborator

Summary

Весь слой skills/sync не работает из git worktree: пин логина ищется
хождением вверх от CWD, а worktree лежит вне основного чекаута, где нет
.claude/settings.local.json. Каждый скрипт падает с «no login pinned» и
предлагает /tea:auth, хотя логин для проекта запинен и tea в том же
каталоге отрабатывает нормально.

Spec

none

Steps to reproduce

  1. Завести worktree рядом с чекаутом — обычный git-поток:
    git worktree add -b fix/something ../tea-worktrees/something origin/main
    cd ../tea-worktrees/something
    
  2. Позвать любой скрипт слоя sync оттуда:
    python3 skills/sync/scripts/remote.py --state all --limit 8 --out /tmp/store
    
  3. То же самое на пути «создать issue и опубликовать» — это основной сценарий:
    python3 skills/issue/scripts/issue_new.py --type bug --title "Some bug"
    python3 skills/sync/scripts/push.py some-bug
    
  4. Для контраста — в том же каталоге позвать tea напрямую:
    tea api --login "$GITEA_LOGIN" repos/claude-skills/tea
    

Expected

Шаг 2 отрабатывает. Логин запинен для проекта, скрипт лежит внутри репозитория
проекта, и store_root() из соседнего слоя этот репозиторий находит — значит
найти его умеет и транспорт.

Actual

Шаг 2:

remote.py: no login pinned (.claude/settings.local.json env.GITEA_LOGIN). Run /tea:auth.

Шаг 4 при этом успешно отдаёт JSON: хук tea-guard подставляет пин, потому что
читает его от каталога проекта, а не от CWD. То есть в одном и том же каталоге
tea логин видит, а скрипты — нет, и сообщение зовёт запинить то, что уже
запинено.

--dry-run проходит целиком — сети он не касается, — так что отказ всплывает
ровно в тот момент, когда работа готова к отправке.

Три следствия, и каждое хуже предыдущего:

Совет в тексте ошибки ведёт не туда. /tea:auth внутри worktree создаст
второй settings.local.json в каталоге, который будет удалён вместе с
worktree, — и попросит оператора выбирать логин, который уже выбран.

Обходной путь неочевиден и нигде не записан: запускать скрипты worktree с
CWD внутри основного чекаута.

cd /path/to/main/checkout
python3 /path/to/worktree/skills/sync/scripts/push.py <slug>

Обходной путь портит данные. Стор резолвится от __file__, то есть внутрь
worktree, но не всегда: часть скриптов требует явного
--out <worktree>/tmp/issues, иначе запись уходит в чужой стор — а стор
оператора трогать нельзя. И главное: push.py:202-214 (git_branch) читает
ветку по CWD, то есть ветку основного чекаута, и в Gitea уезжает ref: main
вместо ветки, на которой работа реально живёт. Ровно то поле, ради которого
branch: существует.

Environment

Асимметрия внутри одного репозитория, и правильная половина уже написана:

  • skills/issue/scripts/issue.py:97store_root() идёт вверх от __file__
    (issue.py:78) до ближайшего .git или AGENTS.md. Работает из любого
    каталога, worktree в том числе; issue.repo_root (issue.py:81-95)
    специально умеет читать .git как файл.
  • skills/sync/scripts/_gitea.py:51find_pin(start_dir=None) идёт вверх от
    start_dir or ".".
  • skills/sync/scripts/_gitea.py:72require_login() зовёт
    find_pin(os.getcwd()), и другого источника у него нет.

Порядок поиска у хука и у скриптов разный, и записан он в двух местах:

где откуда идёт поиск
hooks/tea-guard.sh:120 CLAUDE_PROJECT_DIR, затем cwd из payload, затем os.getcwd()
skills/sync/scripts/_gitea.py:72 только os.getcwd()

В worktree обе попытки пусты — ни от CWD, ни от каталога скрипта
.claude/settings.local.json не находится, потому что он лежит в основном
чекауте и не отслеживается git:

find_pin(cwd):        None
find_pin(script dir): None
store_root:           <worktree>/tmp/issues

Что worktree — это дерево того же репозитория, git знает: .git в нём файл,

$ cat .git
gitdir: /path/to/tea/.git/worktrees/evict-closed-issues

а git rev-parse --git-common-dir указывает на общий каталог, рядом с которым
и лежит настоящий корень проекта. Ни того ни другого транспорт сейчас не
спрашивает.

Задевает это ровно то, ради чего скрипты и написаны: работа над issue в
отдельной ветке. Хук tea-guard от проблемы не страдает, так что несогласие
двух путей резолва видно только по ошибке скрипта. Затронуты все скрипты,
зовущие _gitea.require_login: pull.py, push.py, comment.py, remote.py,
labels.py, wiki_*.py.

Воспроизведено тремя независимыми агентами на ветках main,
fix/limit-bounds-the-write и fix/no-store-for-label-payloads.
tea 0.14.1/0.14.2, Python 3.14.5, macOS 25.5.0.

Constraints

Резолв не переносится на __file__. Плагин, установленный вне репозитория
и наведённый на чужой проект, не должен смотреть на свой собственный каталог —
поиск остаётся от CWD вверх, а worktree добавляется отдельной веткой поиска
через gitdir: из файла .git.

Acceptance criteria

  • любой скрипт skills/sync/scripts/ находит пин, будучи запущенным из
    worktree с CWD внутри него
  • пин из основного чекаута виден из worktree того же репозитория, и найден
    он через gitdir: в файле .git, а не через __file__
  • поиск по-прежнему идёт от CWD вверх; каталог самого плагина источником не
    становится
  • порядок поиска у скриптов и у хука один и тот же, и он записан в одном
    месте
  • второй settings.local.json в worktree не создаётся
  • branch: у issue, отправленной из worktree, равен ветке worktree, а не
    ветке основного чекаута
  • доменный слой не задет: skills/issue/scripts/ и skills/page/scripts/
    по-прежнему stdlib-only и про логин не знают
  • сообщение «no login pinned» остаётся для случая, когда пина правда нет —
    оно не должно превращаться в «нашли не тот»
  • тест: временный репозиторий с .claude/settings.local.json, worktree
    рядом с ним, запуск оттуда находит логин; без файла — по-прежнему ошибка
  • тест: push.py из worktree отправляет branch: этого worktree
<!-- tea:id login-pin-in-a-git-worktree --> ## Summary Весь слой `skills/sync` не работает из git worktree: пин логина ищется хождением вверх от CWD, а worktree лежит вне основного чекаута, где нет `.claude/settings.local.json`. Каждый скрипт падает с «no login pinned» и предлагает `/tea:auth`, хотя логин для проекта запинен и `tea` в том же каталоге отрабатывает нормально. ## Spec none ## Steps to reproduce 1. Завести worktree рядом с чекаутом — обычный git-поток: ```bash git worktree add -b fix/something ../tea-worktrees/something origin/main cd ../tea-worktrees/something ``` 2. Позвать любой скрипт слоя sync оттуда: ```bash python3 skills/sync/scripts/remote.py --state all --limit 8 --out /tmp/store ``` 3. То же самое на пути «создать issue и опубликовать» — это основной сценарий: ```bash python3 skills/issue/scripts/issue_new.py --type bug --title "Some bug" python3 skills/sync/scripts/push.py some-bug ``` 4. Для контраста — в том же каталоге позвать `tea` напрямую: ```bash tea api --login "$GITEA_LOGIN" repos/claude-skills/tea ``` ## Expected Шаг 2 отрабатывает. Логин запинен для проекта, скрипт лежит внутри репозитория проекта, и `store_root()` из соседнего слоя этот репозиторий находит — значит найти его умеет и транспорт. ## Actual Шаг 2: ``` remote.py: no login pinned (.claude/settings.local.json env.GITEA_LOGIN). Run /tea:auth. ``` Шаг 4 при этом успешно отдаёт JSON: хук `tea-guard` подставляет пин, потому что читает его от каталога проекта, а не от CWD. То есть в одном и том же каталоге `tea` логин видит, а скрипты — нет, и сообщение зовёт запинить то, что уже запинено. `--dry-run` проходит целиком — сети он не касается, — так что отказ всплывает ровно в тот момент, когда работа готова к отправке. Три следствия, и каждое хуже предыдущего: **Совет в тексте ошибки ведёт не туда.** `/tea:auth` внутри worktree создаст второй `settings.local.json` в каталоге, который будет удалён вместе с worktree, — и попросит оператора выбирать логин, который уже выбран. **Обходной путь неочевиден и нигде не записан**: запускать скрипты worktree с CWD внутри основного чекаута. ```bash cd /path/to/main/checkout python3 /path/to/worktree/skills/sync/scripts/push.py <slug> ``` **Обходной путь портит данные.** Стор резолвится от `__file__`, то есть внутрь worktree, но не всегда: часть скриптов требует явного `--out <worktree>/tmp/issues`, иначе запись уходит в чужой стор — а стор оператора трогать нельзя. И главное: `push.py:202-214` (`git_branch`) читает ветку по CWD, то есть ветку основного чекаута, и в Gitea уезжает `ref: main` вместо ветки, на которой работа реально живёт. Ровно то поле, ради которого `branch:` существует. ## Environment Асимметрия внутри одного репозитория, и правильная половина уже написана: - `skills/issue/scripts/issue.py:97` — `store_root()` идёт вверх от `__file__` (`issue.py:78`) до ближайшего `.git` или `AGENTS.md`. Работает из любого каталога, worktree в том числе; `issue.repo_root` (`issue.py:81-95`) специально умеет читать `.git` как файл. - `skills/sync/scripts/_gitea.py:51` — `find_pin(start_dir=None)` идёт вверх от `start_dir or "."`. - `skills/sync/scripts/_gitea.py:72` — `require_login()` зовёт `find_pin(os.getcwd())`, и другого источника у него нет. Порядок поиска у хука и у скриптов разный, и записан он в двух местах: | где | откуда идёт поиск | |---|---| | `hooks/tea-guard.sh:120` | `CLAUDE_PROJECT_DIR`, затем `cwd` из payload, затем `os.getcwd()` | | `skills/sync/scripts/_gitea.py:72` | только `os.getcwd()` | В worktree обе попытки пусты — ни от CWD, ни от каталога скрипта `.claude/settings.local.json` не находится, потому что он лежит в основном чекауте и не отслеживается git: ``` find_pin(cwd): None find_pin(script dir): None store_root: <worktree>/tmp/issues ``` Что worktree — это дерево того же репозитория, git знает: `.git` в нём файл, ``` $ cat .git gitdir: /path/to/tea/.git/worktrees/evict-closed-issues ``` а `git rev-parse --git-common-dir` указывает на общий каталог, рядом с которым и лежит настоящий корень проекта. Ни того ни другого транспорт сейчас не спрашивает. Задевает это ровно то, ради чего скрипты и написаны: работа над issue в отдельной ветке. Хук `tea-guard` от проблемы не страдает, так что несогласие двух путей резолва видно только по ошибке скрипта. Затронуты все скрипты, зовущие `_gitea.require_login`: `pull.py`, `push.py`, `comment.py`, `remote.py`, `labels.py`, `wiki_*.py`. Воспроизведено тремя независимыми агентами на ветках `main`, `fix/limit-bounds-the-write` и `fix/no-store-for-label-payloads`. tea 0.14.1/0.14.2, Python 3.14.5, macOS 25.5.0. ## Constraints Резолв **не** переносится на `__file__`. Плагин, установленный вне репозитория и наведённый на чужой проект, не должен смотреть на свой собственный каталог — поиск остаётся от CWD вверх, а worktree добавляется отдельной веткой поиска через `gitdir:` из файла `.git`. ## Acceptance criteria - [ ] любой скрипт `skills/sync/scripts/` находит пин, будучи запущенным из worktree с CWD внутри него - [ ] пин из основного чекаута виден из worktree того же репозитория, и найден он через `gitdir:` в файле `.git`, а не через `__file__` - [ ] поиск по-прежнему идёт от CWD вверх; каталог самого плагина источником не становится - [ ] порядок поиска у скриптов и у хука один и тот же, и он записан в одном месте - [ ] второй `settings.local.json` в worktree не создаётся - [ ] `branch:` у issue, отправленной из worktree, равен ветке worktree, а не ветке основного чекаута - [ ] доменный слой не задет: `skills/issue/scripts/` и `skills/page/scripts/` по-прежнему stdlib-only и про логин не знают - [ ] сообщение «no login pinned» остаётся для случая, когда пина правда нет — оно не должно превращаться в «нашли не тот» - [ ] тест: временный репозиторий с `.claude/settings.local.json`, worktree рядом с ним, запуск оттуда находит логин; без файла — по-прежнему ошибка - [ ] тест: `push.py` из worktree отправляет `branch:` этого worktree
claude added this to the Self-improvement milestone 2026-08-10 12:28:42 +00:00
claude added the comp/sync
severity
medium
type
bug
labels 2026-08-10 12:28:42 +00:00
naudachu added the comp/tea-guard label 2026-08-10 12:38:07 +00:00
claude changed reference from fix/limit-bounds-the-write to fix/limit-bounds-the-write 2026-08-10 12:51:23 +00:00
Sign in to join this conversation.