What
needs-human.sh's once-per-episode comment guard derives 'fresh episode' from the presence of the needs-human label on the target (needs-human.sh:79-80). But ALL label writes silently fail on this environment, each swallowed by || true:
Label never sticks → every tick reads 'no label' → fresh episode → comment posts EVERY TICK. PR #168 accumulated 99 identical '(not yet reviewed)' comments overnight (one per ~5-6 min tick); the label list on the PR is empty. Same mechanism explains the silently-missing needs-human label on escalated issues #96/#106/#124 earlier this week.
Fix
Replace all label reads/writes in needs-human.sh with REST via gh api (proven to work with this token/gh version):
- ensure label:
gh api -X POST repos/{owner}/{repo}/labels -f name=needs-human -f color=b60205 (ignore 422 already-exists)
- read:
gh api repos/{owner}/{repo}/issues/N -q '[.labels[].name]' (PRs are issues)
- add:
echo '{"labels":["needs-human"]}' | gh api -X POST repos/{owner}/{repo}/issues/N/labels --input -
- remove:
gh api -X DELETE repos/{owner}/{repo}/issues/N/labels/needs-human (ignore 404)
Plus the #139 principle: if the add still fails, FAIL LOUDLY (log to the tick record / events.jsonl), never fall through to posting a comment on an unverifiable episode — the comment must only post when the label write is CONFIRMED (that's the guard's persistence).
Tests: extend the needs-human/pr-comment-fix test stubs to assert the REST call shapes and the no-comment-on-failed-label-write invariant.
Cleanup
The 99 spam comments on PR #168 (and any siblings on other loop PRs, e.g. #161) can be bulk-deleted via gh api -X DELETE repos/{owner}/{repo}/issues/comments/{id}.
Related: #139 (fail-loudly label bootstrap), #99 (original seam; its review explicitly rejected per-tick spam — this bug reintroduced it through the back door).
🤖 Generated with Claude Code
What
needs-human.sh's once-per-episode comment guard derives 'fresh episode' from the presence of the needs-human label on the target (needs-human.sh:79-80). But ALL label writes silently fail on this environment, each swallowed by
|| true:gh label create needs-human ...(line 99): gh 2.4.0 has NOgh labelcommand.gh pr edit N --add-label/gh issue edit N --add-label(lines 110/114) and the --remove-label pair (131/132): GraphQL scope error with the bot token on gh 2.4.0 (same failure the issue Loop: react to post-open PR events — remote CI failure fixing, review-comment convergence, conflict-after-sibling-merge #96 implementer hit on 2026-07-16 and worked around via REST).Label never sticks → every tick reads 'no label' → fresh episode → comment posts EVERY TICK. PR #168 accumulated 99 identical '(not yet reviewed)' comments overnight (one per ~5-6 min tick); the label list on the PR is empty. Same mechanism explains the silently-missing needs-human label on escalated issues #96/#106/#124 earlier this week.
Fix
Replace all label reads/writes in needs-human.sh with REST via gh api (proven to work with this token/gh version):
gh api -X POST repos/{owner}/{repo}/labels -f name=needs-human -f color=b60205(ignore 422 already-exists)gh api repos/{owner}/{repo}/issues/N -q '[.labels[].name]'(PRs are issues)echo '{"labels":["needs-human"]}' | gh api -X POST repos/{owner}/{repo}/issues/N/labels --input -gh api -X DELETE repos/{owner}/{repo}/issues/N/labels/needs-human(ignore 404)Plus the #139 principle: if the add still fails, FAIL LOUDLY (log to the tick record / events.jsonl), never fall through to posting a comment on an unverifiable episode — the comment must only post when the label write is CONFIRMED (that's the guard's persistence).
Tests: extend the needs-human/pr-comment-fix test stubs to assert the REST call shapes and the no-comment-on-failed-label-write invariant.
Cleanup
The 99 spam comments on PR #168 (and any siblings on other loop PRs, e.g. #161) can be bulk-deleted via
gh api -X DELETE repos/{owner}/{repo}/issues/comments/{id}.Related: #139 (fail-loudly label bootstrap), #99 (original seam; its review explicitly rejected per-tick spam — this bug reintroduced it through the back door).
🤖 Generated with Claude Code