fix(skills): сторож зависшего треда развязан с веткой исполнителя
- Зачем:
- сторож был зашит на task-result.md и task-stdout.jsonl, то есть на
артефакты этапа 3. На ревью и на починке отчёт исполнителя уже лежит
рядом, цикл не делал ни одного тика и выходил молча — страховка
отсутствовала ровно там, где прогон длиннее всего.
- отсутствующий лог был неотличим от неростущего: `|| echo 0` выдавал
ноль два тика подряд, и неверный путь читался как застой.
- Что:
- пара файлов вынесена в переменные REPORT и LOG первой строкой, с
указанием подставлять их от страхуемой ветки.
- отсутствие лога стало отдельным сигналом NOLOG (редирект создаёт лог
сразу, так что через тик его не быть не может).
- дописано, что выход из цикла снимает страховку: после STALLED сторож
перезапускают руками.
- поправлено описание ловушки bfs: он ругается в stderr, а пустым
отдаёт только stdout — «молча» было неверно.
- Проверка:
- симуляция четырёх исходов: штатное завершение, зависание, неверный
путь к логу (NOLOG) и ветка ревью при лежащем рядом отчёте этапа 3 —
последняя как раз и не работала до правки.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
05336325db
commit
78ee8bfa48
1 file changed
+22
-15
@@ -52,33 +52,40 @@ WSL2 Ubuntu: exec/resume/`-o`/фоновый Bash — весь конвейер)
|
||||
его реальному выходу.
|
||||
- **О зависании фоновый запуск молчит**: он будит по выходу, а повисший тред
|
||||
не выходит — живой процесс и работающий процесс снаружи неразличимы.
|
||||
Признак жизни — рост `stdout.jsonl`, а не статус (2026-07-07). Долгий
|
||||
прогон страхуй сторожем — вторым фоновым Bash, который выходит либо когда
|
||||
Признак жизни — рост JSONL-лога, а не статус (2026-07-07). Долгий прогон
|
||||
страхуй сторожем — вторым фоновым Bash, который выходит либо когда
|
||||
появился `-o`-отчёт, либо когда лог перестал расти. Сторож ходит по
|
||||
артефактам, а не по pid: фоновый Bash отдаёт id задачи харнесса, а pid
|
||||
процесса тебе неоткуда взять.
|
||||
процесса тебе неоткуда взять. Пара файлов — **от той ветки, которую
|
||||
страхуешь** (у ревью `review-impl-*`, у починки `fix-*`): подставь их в
|
||||
первую строку, иначе сторож проверит чужой отчёт, найдёт его на месте и
|
||||
выйдет, не сделав ни одного тика.
|
||||
|
||||
```bash
|
||||
REPORT="$D/task-result.md"; LOG="$D/task-stdout.jsonl" # ветка исполнителя
|
||||
prev=-1
|
||||
while [ ! -s "$D/task-result.md" ]; do
|
||||
while [ ! -s "$REPORT" ]; do
|
||||
sleep 300
|
||||
[ -s "$D/task-result.md" ] && break
|
||||
cur=$(wc -c < "$D/task-stdout.jsonl" 2>/dev/null || echo 0)
|
||||
[ -s "$REPORT" ] && break
|
||||
[ -e "$LOG" ] || { echo NOLOG; break; }
|
||||
cur=$(wc -c < "$LOG")
|
||||
[ "$cur" = "$prev" ] && { echo STALLED; break; }
|
||||
prev=$cur
|
||||
done
|
||||
```
|
||||
|
||||
Две ловушки, обе проверены на симуляции 2026-08-03. Выход проверяй **и
|
||||
после `sleep`** — иначе сторож объявит застой ровно в тот тик, когда
|
||||
Кодекс дописывал отчёт. И не переводи признак жизни на относительные
|
||||
метки времени: `find -newermt '-10 minutes'` под `bfs` (подмена `find` в
|
||||
части дистрибутивов) молча отдаёт пустой stdout — сторож закричит на
|
||||
первом же тике.
|
||||
Проверено симуляцией 2026-08-03: выход проверяй **и после `sleep`**, иначе
|
||||
сторож объявит застой в тот самый тик, когда Кодекс дописывал отчёт. И не
|
||||
переводи признак жизни на относительные метки времени:
|
||||
`find -newermt '-10 minutes'` под `bfs` (подмена `find` в части
|
||||
дистрибутивов) ругается в stderr, а в подстановку команды отдаёт пустой
|
||||
stdout — ошибка проглотится, и сторож закричит на первом же тике.
|
||||
|
||||
`STALLED` — повод посмотреть, а не вердикт: длинная команда внутри
|
||||
песочницы (сборка, тесты) событий не пишет. Разбудил — прочти последнюю
|
||||
строку JSONL и решай сам.
|
||||
`NOLOG` — путь неверен или запуск не состоялся: редирект создаёт лог сразу,
|
||||
так что через тик его не быть не может. `STALLED` — повод посмотреть, а не
|
||||
вердикт: длинная команда внутри песочницы (сборка, тесты) событий не пишет.
|
||||
Разбудил — прочти последнюю строку JSONL и решай; решил, что жив, —
|
||||
перезапусти сторож: выходом из цикла он страховку снял.
|
||||
- **thread_id — в первой строке stdout-JSONL** (`{"type":"thread.started",...}`).
|
||||
Забирай сразу после старта: это страховка — если процесс умер или его
|
||||
прибил хост, тред жив: `codex exec resume <id> «дай финальный отчёт»`.
|
||||
|
||||
Reference in new issue
Block a user