Repository navigation
fix(kanban): treat a phantom/deleted PR URL as not_found so it never wedges active_pr - #100
Merged
Merged
Conversation
…r wedges active_pr
A PR URL that GitHub definitively does not have — never created, or deleted
— makes `gh pr view` exit nonzero with the GraphQL signature "Could not
resolve to a PullRequest". `_resolve_pr_state` failed open to "unknown" on
ANY nonzero exit, and the active_pr respawn guard treats "unknown" as still-
active, so a phantom PR URL in a task comment guarded the card on every tick
indefinitely (dozens of respawn_guarded {active_pr} events, no worker ever
spawned).
Root cause: conflating a DEFINITIVE not-found (terminal — a nonexistent PR
can never be active work) with a TRANSIENT gh failure (network / auth /
gh-missing — genuinely unresolvable, must stay fail-open).
Add a terminal "not_found" state: _resolve_pr_state returns it when gh exits
nonzero AND stderr carries the not-found signature (_PR_NOT_FOUND_STDERR_RE);
every other nonzero exit still returns "unknown" (fail-open preserved).
check_respawn_guard's dup-PR decision consults a single-source-of-truth
_RESPAWN_GUARD_INACTIVE_PR_STATES = {closed, merged, not_found}, so a
not-found PR no longer guards while open/unknown still do.
Tests: 3 new behavior-contract tests (not-found stderr maps to not_found; a
generic nonzero exit still maps to unknown; a not-found PR-URL comment from a
real builder clears the guard). Also makes the route-feedback fixture stub
_resolve_pr_state->open so its placeholder PR URL is deterministic instead of
resolving not_found against live GitHub.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A PR URL that GitHub definitively does not have — one that was never created, or was deleted — makes
gh pr viewexit nonzero with the GraphQL signatureCould not resolve to a PullRequest._resolve_pr_statefailed open to"unknown"on any nonzero exit, and theactive_prrespawn guard treats"unknown"as still-active (conservative). So a phantom/stale PR URL sitting in a task comment guarded the card on every dispatcher tick, indefinitely — dozens ofrespawn_guarded {active_pr}events with no worker ever spawning.This is an orthogonal residual hole not covered by the earlier
active_prfixes (NousResearch#46204 explicit-unblock cutoff, NousResearch#46549 review-lane bypass, #77 recovery-actor re-arming comment): all of those still route through_resolve_pr_state, which fails open on the phantom-PR case.Root cause
Conflating a definitive not-found (terminal — a nonexistent PR can never be active work) with a transient
ghfailure (network / auth / gh-missing — genuinely unresolvable, must stay fail-open).Fix
_PR_NOT_FOUND_STDERR_RE(could not resolve to a pullrequest, case-insensitive)._resolve_pr_statereturns a new terminal state"not_found"whenghexits nonzero and stderr carries that signature. Every other nonzero exit still returns"unknown"— fail-open behavior is preserved for genuine transient failures.check_respawn_guard's dup-PR decision now consults a single-source-of-truth_RESPAWN_GUARD_INACTIVE_PR_STATES = frozenset({"closed", "merged", "not_found"}). A not-found PR no longer guards, exactly like a closed/merged one;openandunknownstill guard.Minimal, single-file production change in
hermes_cli/kanban_db.py.Tests
RED-then-GREEN, in
tests/hermes_cli/test_kanban_db.py:_resolve_pr_statemaps the GraphQL not-found stderr →not_found.unknown(fail-open intact).None.Plus one hermetic-fixture fix in
tests/hermes_cli/test_kanban_route_feedback_to_author.py: thekanban_homefixture now stubs_resolve_pr_state→"open", because its placeholder PR URL is non-existent and would otherwise now resolvenot_foundand clear the very guard those routing tests set out to exercise. The routing logic under test is orthogonal to live PR-state resolution.Verification
Real
kanban_db, tempHERMES_HOME, no mocks. Isolated kanban surface green (310 passed across the four core kanban test files run per-file). Zero new failures — the ~26 cross-test-pollution failures seen in a whole--k kanbansingle-process run reproduce identically on the clean base branch and are unrelated to this change (base: 892 passed / 26 failed; this branch: 895 passed / 26 failed = exactly the 3 new tests added).PATCHES.mdupdated with the carried-patch manifest row (permanent-local; fork-internal dispatcher invariant).