process: a failed read of open issues never files a duplicate (#700 r1)

CODE-REVIEW-700-r1 Medium: `gh issue list … || true` turned a failed read
into an empty answer, and the step went on to gh issue create — a second
[workflow-sync] issue next to the open one on every network or rate-limit
failure. A failed read now warns and exits 0; creating stays reserved for
«read succeeded, nothing open».

Mutant workflow-sync-issue-duplicated-on-read-failure.

Issue: #700
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-29 01:22:14 +03:00
parent a005aae5b6
commit f6e317d871
3 changed files with 21 additions and 2 deletions
+7 -2
View File
@@ -158,8 +158,13 @@ jobs:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
marker="[workflow-sync]"
existing=$(gh issue list --repo "$REPO" --state open --search "\"$marker\" in:title" \
--json number,title --jq '[.[] | select(.title | startswith("[workflow-sync]"))][0].number // empty' || true)
# Список не прочитан — нового issue нет: иначе сбой сети или
# рейт-лимит плодил бы дубликат к уже открытому (r1 #700).
if ! existing=$(gh issue list --repo "$REPO" --state open --search "\"$marker\" in:title" \
--json number,title --jq '[.[] | select(.title | startswith("[workflow-sync]"))][0].number // empty'); then
echo "::warning::открытые issue не прочитаны — новое не заводится, чтобы не завести дубликат"
exit 0
fi
if [ -n "$existing" ]; then
gh issue comment "$existing" --repo "$REPO" --body "Расхождение повторилось: $RUN_URL" \
|| echo "::warning::комментарий в #$existing не оставлен"
+11
View File
@@ -12718,6 +12718,17 @@ const MUTANT_DEFINITIONS = [
replace: ' check "тонкие вызывающие workflow в main и dev" "$WORKFLOW_SYNC"',
}],
},
{
id: 'workflow-sync-issue-duplicated-on-read-failure',
guard: 'node --test --test-name-pattern="#700: на ветке задачи" test/validate-workflow.test.mjs',
because: 'r1 #700: a failed read of the open issues must not fall through into gh issue create — '
+ 'that duplicates the owner issue on every network or rate-limit failure',
patches: [{
file: '.github/workflows/validate.yml',
find: ' exit 0\n fi\n if [ -n "$existing" ]; then',
replace: ' :\n fi\n if [ -n "$existing" ]; then',
}],
},
{
id: 'external-link-warn-mode-ignored',
guard: 'node --test --test-name-pattern="#700: check-docs" test/validate-workflow.test.mjs',
+3
View File
@@ -628,6 +628,9 @@ test('#700: на ветке задачи зеркало workflow и внешни
const issue = preflight.slice(preflight.indexOf('- name: "Расхождение зеркала на dev — issue владельцу"'));
assert.match(issue, /if: github\.event_name == 'push' && github\.ref == 'refs\/heads\/dev' && steps\.workflow_sync\.outcome == 'failure'/);
assert.match(issue, /gh issue list --repo "\$REPO" --state open --search/, 'одно issue, а не одно на каждый push');
// r1 #700: несчитанный список — не повод заводить новое.
assert.match(issue, /if ! existing=\$\(gh issue list [\s\S]*?\); then\n\s+echo "::warning::[^"]*"\n\s+exit 0\n\s+fi/);
assert.doesNotMatch(issue.slice(0, issue.indexOf('gh issue create')), /\|\| true\)/, 'сбой чтения не глушится в пустой ответ');
assert.match(preflight, /permissions:\n\s+contents: read\n\s+actions: read\n\s+issues: write/);
});