Skip to content

Grooming a blocker is a one-way door: blockedReason excludes the issue that would clear it #793

Description

@joryirving

Problem

Once the groomer marks an issue blocked or not-ready, that issue can never be groomed again, so the field that parked it can never be cleared. Recording the blocker is what makes the issue unreviewable.

buildGroomingStateExclusionWhere in src/lib/issue-filters.ts excludes any issue with a non-null blockedReason or notReadyReason:

return {
  AND: [
    { OR: [{ groomedAt: null }, { groomedAt: { lt: cutoff } }] },
    { blockedReason: null },
    { notReadyReason: null },
  ],
};

selectGroomingCandidate in src/lib/groomer/selector.ts applies that unconditionally. Those two fields are cleared only inside the groom route in src/app/api/issues/groom/route.ts, which runs only on an issue the selector picked. That is a closed loop.

Note the asymmetry with the neighbouring clause: the groomedAt cooldown is time-bounded and self-clears after 24h. The blocked/not-ready exclusion has no clearing condition at all.

There is also no manual escape. The exclusion is appended after the options.issueNumber filter is applied, so a targeted re-groom of a specific blocked issue also returns nothing.

Evidence

misospace/alert-triage#36. The groomer parked it on 2026-08-13 and identified its prerequisites correctly:

groomedBy:         hosted-groomer
groomedAt:         2026-08-13T05:58:37Z
blockedReason:     "Blocked by unresolved dependencies and validation gate ..."
needsInfoReason:   "Dependencies #34 for path, #35 for diff, and prerequisite #17
                    for evidence signal are not confirmed resolved ..."
nextGroomingAction: "mark_needs_info"

All three prerequisites have since closed: alert-triage#34 on 2026-08-14, #17 on 2026-08-14, #35 on 2026-08-16. The issue has been genuinely unblocked since 08-16 and still carries status/backlog with groomedAt frozen at 08-13. It is invisible to the queue because src/lib/agent-queue.ts filters status/backlog.

The intent to revisit is already recorded and unread. route.ts sets nextGroomingAction to "Revisit once the blocking condition is resolved" for mark_not_ready and "Resolve the blocking dependency" for mark_blocked. Nothing consumes either string.

Scope note: dispatch#765 is in the same state, but its blockedReason is an accepted risk rather than a dependency, so it is parked correctly. The bug is that nothing can revisit it either.

Proposal

Any one of these fixes the deadlock; the third is the one that matches what the groomer already knows.

  1. Make options.issueNumber bypass the grooming-state exclusion, so a targeted re-groom is possible. Minimum viable, restores a manual lever.
  2. Give blocked/not-ready a cooldown the way groomedAt has one, so parked issues re-enter the candidate pool periodically instead of never.
  3. Parse #N references out of blockedReason and needsInfoReason and make the issue eligible again once every referenced issue is closed. The groomer already writes those numbers.

Acceptance

  • An issue with a non-null blockedReason can be re-groomed, by whichever trigger is chosen.
  • alert-triage#36 specifically becomes selectable again and is re-evaluated against its now-closed prerequisites.
  • A test covers the deadlock directly: set blockedReason, assert the issue is still reachable by the selector under the chosen condition.
  • dispatch#765 stays parked, since its blocker is an accepted risk rather than a dependency, but is re-evaluated rather than excluded outright.

Files: src/lib/issue-filters.ts (buildGroomingStateExclusionWhere is the exclusion), src/lib/groomer/selector.ts (selectGroomingCandidate applies it after the issueNumber filter), src/app/api/issues/groom/route.ts (the only place the reason fields are cleared).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions