Conversation
…mirror Refs hermes-agent#2 (extends NousResearch#1). - GH mirror must surface PR-thread comments (Part A: GH_REF_RE accepts /pull/N, _pick_ref recency tie-break, dual-ref mirror_pull, dedupe by (owner, repo, number, comment_id)). - check_respawn_guard must release on reviewer feedback (Part B: non-default author + body length, distinct _content_key, body-pattern match, or reviewDecision == CHANGES_REQUESTED). - Branch-routing on release uses same branch/head SHA as the most recent PR-URL comment. Concrete reproduction: t_de993dac / alerthq#178 on 2026-08-20 - 45 silent respawn_guarded events.
Implements SPEC-active-pr-guard-reviewer-feedback Part B (hermes-agent#2, extends NousResearch#1). The 24h active-PR guard used to silently swallow reviewer feedback on an open PR — the bug that left t_de993dac (PR aliaadil/alerthq#178) silent for 45 minutes on 2026-08-20. Now: - check_respawn_guard checks reviewer-feedback release triggers BEFORE returning active_pr: non-default author + body >= 80 chars, distinct content_key, body-pattern match, or reviewDecision == CHANGES_REQUESTED. - New _compile_word_boundary_re enforces per-word boundaries on the reviewer-feedback phrase list, eliminating false positives where this pr used to match priority / this private method and all actions used to match hall actions. - New branch-routing override: when the guard releases, the dispatched builder lands on the same branch + head SHA as the most recent PR-URL comment (gh pr view --json headRefName,headRefOid, with 5min TTL cache). - Forwarded to the worker via the new pr_head_sha spawn-kwarg (HERMES_KANBAN_PR_HEAD_SHA env var) — no parent-process env mutation, so the SHA can't leak from one task's spawn into the next. Tests: - tests/hermes_cli/test_kanban_review_lifecycle.py: 6 trigger conditions + 4 non-trigger conditions + 2 substring false-positive guards + 1 phrase-still-matches regression guard + 3 branch-override tests (incl. parent-env-leak regression guard). - tests/scripts/test_github_issues_mirror.py (new): cross-repo Part A mirror changes via importlib. Covers GH_REF_RE matching both /issues/ and /pull/, _pick_ref recency tie-break, secondary-ref pairing for dual-ref mirror_pull, dedupe by (owner, repo, N, comment_id) across endpoints, sidecar persistence. Sweep: scripts/run_tests.sh tests/hermes_cli/{test_kanban_db,test_kanban_review_lifecycle,test_kanban_review_lifecycle_complete,test_kanban_review_surfaces,test_kanban_db_init,test_kanban_db_repair,test_kanban_block_kinds,test_kanban_reclaim_claim_lock_guard,test_kanban_blocked_sticky,test_kanban_dispatch_lock,test_kanban_write_txn_busy_retry} tests/scripts/test_github_issues_mirror.py -> 128 passed, 0 failed, 1 skipped (windows_only). Closes hermes-agent#2 (extends NousResearch#1). Part A landed in /opt/data/scripts/github_issues_mirror.py directly per the operator clarification (deployment path, no PR).
Excellent spec discipline (empirical repro timeline, explicit non-trigger criteria) and the implementation mostly honors it — the per-word-boundary phrase compiler kills the real substring false positives ("this priority", "hall actions"), dual-ref mirroring with cross-endpoint dedupe is tested, and routing branch+SHA through a spawn kwarg instead of process env avoids the cross-task leak. Two trigger-design holes and two smaller points:
No blocking issues found beyond items 1–2, which together decide whether this ships as a fix or as a new silent-respawn generator. |
…umber regex on non-default author Addresses the two blocking findings from the AI code review on PR NousResearch#91208 (hermes-agent#2): 1. _REVIEWER_FEEDBACK_PR_NUM_RE was author-agnostic, so a default- authored auto-mirrored ping like 'PR NousResearch#178 opened' could release the active_pr guard. Spec criterion violated: 'auto-mirrored default-authored status comments do NOT release'. Fix: _has_reviewer_feedback gains an optional author= parameter; when passed, the PR-number regex and pr_url match are skipped for author == 'default'. Phrase matches stay author-agnostic because 'please update' / 'fix in this pr' are concrete reviewer signals unlikely to appear in mirrored status pings. 2. After a release, the same stale feedback re-release the guard on every subsequent tick — a respawn loop bounded only by the failure circuit breaker (reviewer concern: 'Trigger 1 fires ... and after a released respawn fails without a new PR-URL comment, the same stale feedback re-releases the guard on every subsequent tick'). Fix: emit a respawn_released event with the feedback's created_at timestamp on each release. Subsequent ticks read MAX(feedback_at) from prior respawn_released events as a watermark and skip comments at or before it. Trigger 4 (CHANGES_REQUESTED) also gated; uses now() so a follow-up reviewer comment after the decision still fires. Also adds audit fields (reason, feedback_at, trigger=a/b/c/d) so operators can trace why a spawn was allowed without grepping logs. Smaller polish from the same review: - _dispatch_once_locked branch-override pop now logs a debug message when the override is dropped for non-worktree tasks (was previously silent). Tests: - +17 new tests covering: default-author PR-number pings don't release (4 forms); stale-feedback watermark prevents respawn loop; newer feedback re-releases; trigger-4 dedupe after first release; default ping doesn't advance the watermark; release event audit fields; same-timestamp re-mirror doesn't re-release. - All 46 review-lifecycle tests pass; ruff clean.
|
Pushed commit Issue #1 — author-agnostic PR-number regex. Issue #2 — respawn loop on stale feedback. After a release, the same stale feedback would re-fire trigger 1/3 on every subsequent tick — bounded only by the failure circuit breaker. Fix: each release now emits a Audit trail. The new Polish. Tests. +17 new tests (4 default-author PR-number ping forms don't release; stale-feedback watermark prevents the respawn loop; newer feedback re-releases; trigger-4 dedupe after first release; default ping doesn't advance the watermark; release event audit fields; same-timestamp re-mirror doesn't re-release). 46/46 review-lifecycle tests pass. Ruff clean. PR is ready for re-review. |
Closes hermes-agent#2 (extends #1).
Problem
The 24h
active_prrespawn guard inhermes_cli/kanban_db.py:check_respawn_guardsilently swallowed reviewer feedback on an open PR. Reproduced 2026-08-20 ont_de993dac(aliasaliaadil/alerthq#174, PRaliaadil/alerthq#178): the operator left a 19:10:04 UTC reviewer comment on the PR, but the task sat inactive_prfor 45 minutes because the guard has no exception for "reviewer feedback exists after that PR URL."Combined with the mirror bug (PR-thread comments never reached the kanban — fixed separately in
/opt/data/scripts/github_issues_mirror.py, no PR per operator clarification), the reviewer-feedback loop was silently broken.Fix
Part B — guard release triggers
In
check_respawn_guard, before returning"active_pr", check whether any comment whosecreated_atis after the most recent PR-URL comment satisfies any of:len(body) >= 80(rules out auto-mirrored status pings — they'redefault-authored and short)_content_keyfrom the prior PR-URL comment (idempotent re-push does NOT release)please update,fix in this pr,also address,needs to,this pr,not sufficient,all actions,all clicks,please add,please include, OR references the PR number directly (#178/PR #178/pull/178)reviewDecision == 'CHANGES_REQUESTED'(viagh pr view <url> --json reviewDecision)If any trigger fires, return
None(allow the respawn). Otherwise keep"active_pr".Word-boundary tightening
this prwas matching insidepriority/this private method, andall actionsinsidehall actions/small action. The legacy alternation compiled each phrase withre.escapebut didn't anchor on word boundaries. New_compile_word_boundary_retokenizes each phrase on whitespace and wraps each token in\b— multi-word phrases still match their original prose (please update the doc→ match), but substrings inside longer words don't. Verified against the four existing pattern-match tests + 3 new substring-false-positive guards + 1 phrase-still-matches regression test.Branch-routing override
When the guard releases due to reviewer feedback, the dispatched Builder must use the same branch and head SHA as the most recent PR-URL comment — not a fresh
feat/<task-id>-<new-slug>branch. Implementation:gh pr view <url> --json headRefName,headRefOidlookup, gated to the worktree workspace_kind(branch, head_sha)through the newpr_head_shaspawn-kwarg (no parent-process env mutation → no SHA leak between tasks in the same tick)HERMES_KANBAN_PR_HEAD_SHAenv var and cangit reset --hardto that exact commitTests
tests/hermes_cli/test_kanban_review_lifecycle.py:priority/private,hall actions)tests/scripts/test_github_issues_mirror.py(NEW):GH_REF_REmatches both/issues/<N>and/pull/<N>URLs_pick_refrecency tie-break (freshest across body+title+comments wins, full-URL beats short-ref on ties)_secondary_ref_for_taskdual-ref pairing (issue ↔ PR)mirror_pullappends from both refs(owner, repo, N, comment_id)across endpointsSweep:
scripts/run_tests.sh tests/hermes_cli/{test_kanban_db,test_kanban_review_lifecycle,test_kanban_review_lifecycle_complete,test_kanban_review_surfaces,test_kanban_db_init,test_kanban_db_repair,test_kanban_block_kinds,test_kanban_reclaim_claim_lock_guard,test_kanban_blocked_sticky,test_kanban_dispatch_lock,test_kanban_write_txn_busy_retry} tests/scripts/test_github_issues_mirror.py→ 128 passed, 0 failed, 1 skipped (windows_only).Out of scope
_RESPAWN_GUARD_PR_WINDOW(24h stays)repos/{o}/{r}/pulls/{n}/comments(issue-style endpoint covers threaded discussion; review-submission comments remain a follow-up if data shows that's the dominant feedback channel)Part A (mirror) landed in
/opt/data/scripts/github_issues_mirror.pydirectly per operator clarification (deployment path, no PR).Risk
gh pr viewadds ~100ms per guard-release check (cached for 5min); tick budget stays well under the 60s dispatcher tickHERMES_KANBAN_PR_HEAD_SHAas the ONLY way to receive the head SHA — no env leakage_REVIEW_DECISION_CACHEand_BRANCH_OVERRIDE_CACHEis process-local and TTL-bounded; reviewer feedback that arrives during a 5min cache window won't be re-detected until the cache expires (acceptable — re-spawn is at most one tick behind the comment)