hermes - 💡(How to fix) Fix [Bug]: Kanban active_pr respawn guard has no operator clear path and ignores closed PRs

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

Additional Logs / Traceback (optional)

Root Cause

  1. The dispatcher continued to skip the ready task because the recent comment still contained a PR URL.

Fix Action

Fix / Workaround

Kanban dispatcher can leave a ready task effectively stuck behind the active_pr respawn guard after a worker posts a GitHub PR URL, even when that PR is later closed or explicitly declared irrelevant by the operator.

  • Task is ready.
  • Dispatcher repeatedly emits respawn_guarded {"reason": "active_pr"}.
  • Operator comments that the PR is closed / wrong / not relevant.
  • Dispatcher keeps skipping the task anyway.
  • Workarounds are: wait for the 24h time window, create a duplicate follow-up task, or mutate the SQLite DB/comment history manually.
  1. The dispatcher refused to spawn it repeatedly:

Code Example

PR opened: https://github.com/NousResearch/hermes-agent/pull/29394 (...)

---

[2026-05-20 15:57] respawn_guarded {'reason': 'active_pr'}
   [2026-05-20 15:58] respawn_guarded {'reason': 'active_pr'}
   [2026-05-20 15:59] respawn_guarded {'reason': 'active_pr'}
   ...

---

The active_pr is incorrect. I never intended the change to go back to the hermes-agent repo. I actually closed the PR - this is personal work not upstream origin work

---

gh pr view 29394 --repo NousResearch/hermes-agent --json number,state,closed,mergedAt,url,title,closedAt,headRefName,author
   #29394 state=CLOSED closed=true mergedAt=<nil> closedAt=2026-05-20T17:38:20Z url=https://github.com/NousResearch/hermes-agent/pull/29394

---

# hermes_cli/kanban_db.py
_RESPAWN_GUARD_PR_WINDOW = 86400  # 24 hours
_RESPAWN_GUARD_PR_URL_RE = re.compile(
    r"https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+",
    re.IGNORECASE,
)
...
# GitHub PR URL in a recent comment — prior worker already opened a PR.
pr_cutoff = now - _RESPAWN_GUARD_PR_WINDOW
for c in conn.execute(
    "SELECT body FROM task_comments WHERE task_id = ? AND created_at >= ?",
    (task_id, pr_cutoff),
).fetchall():
    if c["body"] and _RESPAWN_GUARD_PR_URL_RE.search(c["body"]):
        return "active_pr"

---

guard_reason = check_respawn_guard(conn, row["id"])
if guard_reason is not None:
    result.respawn_guarded.append((row["id"], guard_reason))
    ...
    _append_event(conn, row["id"], "respawn_guarded", {"reason": guard_reason})
    continue

---

hermes debug share --local summary:
version:          0.14.0 (2026.5.16) [305a6d2a]
os:               Linux 6.1.0-48-amd64 x86_64
python:           3.11.2
openai_sdk:       2.24.0
profile:          default
hermes_home:      ~/.hermes
model:            gpt-5.5
provider:         openai-codex
terminal:         local
gateway:          running (systemd (system))
platforms:        telegram, slack

Also checked:
- gh authenticated as bdmorin
- repo: NousResearch/hermes-agent
- duplicate issue searches for: active_pr, respawn_guarded, "respawn guard", "GitHub PR URL", "closed PR", "PR URL" kanban

---

Task showed status=ready but repeated events like:

respawn_guarded {'reason': 'active_pr'}

`hermes kanban unblock <task>` is not a fix because the task is already ready; this is not a task status block, it is a dispatcher guard.

---

_RESPAWN_GUARD_PR_WINDOW = 86400
_RESPAWN_GUARD_PR_URL_RE = re.compile(r"https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+", re.IGNORECASE)
...
if c["body"] and _RESPAWN_GUARD_PR_URL_RE.search(c["body"]):
    return "active_pr"

---

hermes kanban clear-guard <task_id> active_pr
  # or
  hermes kanban unblock <task_id> --clear-guards
  # or
  hermes kanban dispatch --ignore-guard active_pr <task_id>

---

{"kind": "respawn_guard_cleared", "payload": {"reason": "active_pr", "by": "operator"}}
RAW_BUFFERClick to expand / collapse

Bug Description

Kanban dispatcher can leave a ready task effectively stuck behind the active_pr respawn guard after a worker posts a GitHub PR URL, even when that PR is later closed or explicitly declared irrelevant by the operator.

The guard currently treats a recent GitHub PR URL in task comments as sufficient evidence that the task has an active PR and should not be respawned. There is no first-class CLI/dashboard action to clear, bypass, refresh, or invalidate that guard, and the guard does not check GitHub PR state.

This creates a bad operator loop:

  • Task is ready.
  • Dispatcher repeatedly emits respawn_guarded {"reason": "active_pr"}.
  • Operator comments that the PR is closed / wrong / not relevant.
  • Dispatcher keeps skipping the task anyway.
  • Workarounds are: wait for the 24h time window, create a duplicate follow-up task, or mutate the SQLite DB/comment history manually.

That cannot be the intended lifecycle. If Hermes is going to infer an active_pr block from a PR URL, it needs a supported recovery path when the inferred state is stale or wrong.

Steps to Reproduce

Observed on a real Kanban task:

  1. A worker completed work and posted a PR URL in a task comment:

    PR opened: https://github.com/NousResearch/hermes-agent/pull/29394 (...)
  2. The task was later unblocked/ready for follow-up work.

  3. The dispatcher refused to spawn it repeatedly:

    [2026-05-20 15:57] respawn_guarded {'reason': 'active_pr'}
    [2026-05-20 15:58] respawn_guarded {'reason': 'active_pr'}
    [2026-05-20 15:59] respawn_guarded {'reason': 'active_pr'}
    ...
  4. The operator closed the PR and added a clarifying task comment:

    The active_pr is incorrect. I never intended the change to go back to the hermes-agent repo. I actually closed the PR - this is personal work not upstream origin work
  5. GitHub confirms the PR is closed and unmerged:

    gh pr view 29394 --repo NousResearch/hermes-agent --json number,state,closed,mergedAt,url,title,closedAt,headRefName,author
    #29394 state=CLOSED closed=true mergedAt=<nil> closedAt=2026-05-20T17:38:20Z url=https://github.com/NousResearch/hermes-agent/pull/29394
  6. The dispatcher continued to skip the ready task because the recent comment still contained a PR URL.

Evidence from the current implementation:

# hermes_cli/kanban_db.py
_RESPAWN_GUARD_PR_WINDOW = 86400  # 24 hours
_RESPAWN_GUARD_PR_URL_RE = re.compile(
    r"https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+",
    re.IGNORECASE,
)
...
# GitHub PR URL in a recent comment — prior worker already opened a PR.
pr_cutoff = now - _RESPAWN_GUARD_PR_WINDOW
for c in conn.execute(
    "SELECT body FROM task_comments WHERE task_id = ? AND created_at >= ?",
    (task_id, pr_cutoff),
).fetchall():
    if c["body"] and _RESPAWN_GUARD_PR_URL_RE.search(c["body"]):
        return "active_pr"

And dispatch just records the guard and skips:

guard_reason = check_respawn_guard(conn, row["id"])
if guard_reason is not None:
    result.respawn_guarded.append((row["id"], guard_reason))
    ...
    _append_event(conn, row["id"], "respawn_guarded", {"reason": guard_reason})
    continue

Expected Behavior

At least one supported operator path should exist. Any of these would be reasonable:

  1. hermes kanban unblock <task> or a new command can explicitly clear/bypass the active_pr guard.
  2. hermes kanban dispatch refreshes linked PR state and does not apply active_pr when the PR is closed/unmerged.
  3. The guard is stored as explicit task metadata with a dismiss/override action instead of inferred every tick from immutable comments.
  4. A dashboard recovery action says something like “Ignore stale PR guard / PR closed” and records an audit event.

Also, if a later operator comment says the PR is incorrect/closed, the system should not require hand-editing SQLite or creating a duplicate task.

Actual Behavior

The guard is purely text/recency based:

  • any GitHub PR URL in a recent task comment triggers active_pr
  • PR state is not checked
  • later comments do not clear it
  • the task remains ready but unspawnable until the 24h window expires
  • there is no obvious CLI command (continue does not exist; unblock is for blocked/scheduled status, not this guard)

Affected Component

Other — Kanban dispatcher / Kanban CLI recovery UX (hermes_cli/kanban_db.py, hermes kanban dispatch, task respawn guard behavior)

Messaging Platform (if gateway-related)

N/A (CLI/Kanban board behavior; surfaced while operating from Slack/dashboard, but root behavior is dispatcher/kernel)

Debug Report

hermes debug share --local summary:
version:          0.14.0 (2026.5.16) [305a6d2a]
os:               Linux 6.1.0-48-amd64 x86_64
python:           3.11.2
openai_sdk:       2.24.0
profile:          default
hermes_home:      ~/.hermes
model:            gpt-5.5
provider:         openai-codex
terminal:         local
gateway:          running (systemd (system))
platforms:        telegram, slack

Also checked:
- gh authenticated as bdmorin
- repo: NousResearch/hermes-agent
- duplicate issue searches for: active_pr, respawn_guarded, "respawn guard", "GitHub PR URL", "closed PR", "PR URL" kanban

Operating System

Debian GNU/Linux 12 (bookworm), Linux 6.1.0-48-amd64

Python Version

Python 3.11.2

Hermes Version

Hermes Agent v0.14.0 (2026.5.16), commit 305a6d2a

Additional Logs / Traceback (optional)

Task showed status=ready but repeated events like:

respawn_guarded {'reason': 'active_pr'}

`hermes kanban unblock <task>` is not a fix because the task is already ready; this is not a task status block, it is a dispatcher guard.

Root Cause Analysis (optional)

check_respawn_guard() in hermes_cli/kanban_db.py infers active_pr from a regex scan of recent task comments:

_RESPAWN_GUARD_PR_WINDOW = 86400
_RESPAWN_GUARD_PR_URL_RE = re.compile(r"https?://github\.com/[^/\s]+/[^/\s]+/pull/\d+", re.IGNORECASE)
...
if c["body"] and _RESPAWN_GUARD_PR_URL_RE.search(c["body"]):
    return "active_pr"

There is no state lookup (gh pr view / GitHub API), no explicit persisted guard object, and no clear/dismiss operator action. Comments are an audit trail, so using them as a live control-plane lock without a countervailing override path makes stale/incorrect PR references sticky until time expiry.

Proposed Fix (optional)

Short-term UX fix:

  • Add an operator command, e.g.:

    hermes kanban clear-guard <task_id> active_pr
    # or
    hermes kanban unblock <task_id> --clear-guards
    # or
    hermes kanban dispatch --ignore-guard active_pr <task_id>
  • Record an audit event like:

    {"kind": "respawn_guard_cleared", "payload": {"reason": "active_pr", "by": "operator"}}

Medium-term correctness fix:

  • Parse/store PR URLs as explicit task metadata when created/commented.
  • For GitHub URLs, refresh PR state before applying active_pr.
  • Do not guard if the PR is closed/unmerged or if a later explicit operator override exists.
  • Surface recovery actions in the dashboard when a task is ready but repeatedly respawn_guarded.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

https://github.com/NousResearch/hermes-agent/pull/29394 <-- this is what led me to this.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING