Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion src/operations_center/entrypoints/maintenance/board_unblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
required" notes for patterns this tool covers. When a new stuck pattern emerges, add a
rule here rather than logging it and waiting.

Applies twelve rules on every run:
Applies thirteen rules on every run:

Rule 1 — DEAD_REMEDIATION_CANCEL
Tasks with label "dead-remediation" OR (executor-signal: sigkill + retry-count ≥ 3)
Expand Down Expand Up @@ -89,6 +89,15 @@
failure is safe to retry once the underlying infrastructure is fixed.
The minimum age avoids re-queuing before the board_worker finishes writing labels.

Rule 8.5 — BACKEND_CAPACITY_PARK
goal/spec-author tasks in Blocked with ``blocked-reason: backend-capacity`` and
a known automatic promotion path → move to Backlog after the same minimum age as
Rule 8. These failures happen when the planner/backend hits a session-limit or
quota condition and returns no structured output; leaving them in Blocked strands
work even after the cooldown clears because the recovery path only promotes from
Backlog. Parking them preserves the retry blocker label until Rule 7 / Rule 9
re-promotes them once dispatch is legal again.

Rule 9 — SPEC_AUTHOR_BACKLOG_PROMOTE
spec-author tasks in Backlog state with no active retry blocker → move to Ready for AI.
Rule 8 (CLEAN_BLOCKED_RETRY) moves budget-exhausted spec-author tasks Blocked → Backlog,
Expand Down Expand Up @@ -841,6 +850,55 @@ def _apply_rules(
}
)

# Rule 8.5 — backend-capacity Blocked tasks that already have an automatic
# Backlog→R4AI recovery path. Park them in Backlog (not Ready) so the
# cooldown-aware promoter can restore them once dispatch is legal again.
is_backend_capacity_blocked = (
state_lower == "blocked"
and (
_has_label(labels, _GOAL_LABEL) or _has_label(labels, _SPEC_AUTHOR_LABEL)
)
and not _has_label(labels, _SELF_MODIFY_APPROVED_LABEL)
and not _has_label(labels, _THIN_GOAL_LABEL)
and not _has_label_prefix(labels, _SIGKILL_SIGNAL_PREFIX)
and not _has_label_prefix(labels, "executor-exit-code:")
and not _has_label_prefix(labels, _BLOCKED_BY_PREFIX)
and _has_label(labels, _BLOCKED_REASON_BACKEND_CAPACITY_LABEL)
)
if is_backend_capacity_blocked:
updated_at = _parse_updated_at(issue)
has_repromotion_path = _has_label(labels, _SPEC_AUTHOR_LABEL) or (
_has_label(labels, _GOAL_LABEL)
and (
(_has_label(labels, _SOURCE_AUTONOMY_LABEL) and _has_label(
labels, _SOURCE_IMPROVE_SUGGESTION_LABEL
))
or (
_has_label(labels, _SOURCE_BOARD_WORKER_LABEL)
and _has_label(labels, _HANDOFF_IMPROVEMENT_LABEL)
)
)
and bool(_label_value(labels, _ORIGINAL_TASK_PREFIX))
)
if (
has_repromotion_path
and updated_at
and (now - updated_at) >= timedelta(minutes=clean_blocked_min_minutes)
):
actions.append(
{
"task_id": task_id,
"title": title,
"rule": "BACKEND_CAPACITY_PARK",
"from_state": state,
"to_state": "Backlog",
"reason": (
"backend-capacity/session-limit failure; park in Backlog so "
"cooldown-aware promotion can retry after the backend resets"
),
}
)

# Rule 9 — spec-author Backlog promotion.
# Rule 8 moves budget-exhausted spec-author tasks Blocked → Backlog, but no watcher
# re-promotes them to R4AI once the gate clears. This rule closes that gap.
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/entrypoints/maintenance/test_board_unblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,41 @@ def test_rule8_park_still_fires_during_cooldown():
assert not retry[0].get("skipped")


def test_rule85_backend_capacity_goal_park_fires_during_cooldown():
parent = _issue("p_cd85", state="Done", labels=["task-kind: improve"])
issue = _issue(
"t_cd85",
state="Blocked",
labels=[
"task-kind: goal",
"source: autonomy",
"source: improve-suggestion",
"original-task-id: p_cd85",
"blocked-reason: backend-capacity",
],
updated_at="2026-05-28T10:00:00+00:00",
)
actions = _apply_rules(
[parent, issue], **_RULES_KWARGS, cooldown_skip_reason=_COOLDOWN_REASON
)
parked = [a for a in actions if a["rule"] == "BACKEND_CAPACITY_PARK"]
assert len(parked) == 1
assert parked[0]["to_state"] == "Backlog"
assert not parked[0].get("skipped")


def test_rule85_backend_capacity_goal_without_repromotion_path_stays_blocked():
issue = _issue(
"t_cd85_nopath",
state="Blocked",
labels=["task-kind: goal", "blocked-reason: backend-capacity"],
updated_at="2026-05-28T10:00:00+00:00",
)
actions = _apply_rules([issue], **_RULES_KWARGS, cooldown_skip_reason=_COOLDOWN_REASON)
parked = [a for a in actions if a["rule"] == "BACKEND_CAPACITY_PARK"]
assert parked == []


def test_open_pr_gate_requeue_deferred_during_cooldown():
issue = _issue(
"t_cd_gate",
Expand Down
Loading