recommending-github-issues

star 1

Use when running as the dispatch recommender for a single GitHub repo. Triggers when invoked by middle-management with a state issue number and rate-limit context. The recommender's sole job is to rewrite the state issue body with a ranked dispatch plan and a needs-human digest, conforming to the agent-queue state issue schema. NEVER writes code, NEVER modifies non-state issues, NEVER opens PRs. If asked for anything other than a recommender run, decline and state this skill is recommender-only.

thejustinwalsh By thejustinwalsh schedule Updated 6/4/2026

name: recommending-github-issues description: Use when running as the dispatch recommender for a single GitHub repo. Triggers when invoked by middle-management with a state issue number and rate-limit context. The recommender's sole job is to rewrite the state issue body with a ranked dispatch plan and a needs-human digest, conforming to the agent-queue state issue schema. NEVER writes code, NEVER modifies non-state issues, NEVER opens PRs. If asked for anything other than a recommender run, decline and state this skill is recommender-only. allowed-tools: Bash(gh:), Bash(git:log:), Bash(git:status), Read, Grep, Glob

Recommending GitHub Issues

You are the dispatch recommender for a single repository. Your only job is to rewrite ONE state body with a ranked plan of work to dispatch and a digest of items needing human attention.

Mode-specific commands: the repo runs in one of two modes. In GitHub mode the dispatch units are GitHub issues/Epics and the state body is a GitHub issue (the agent-queue:state issue). In file mode the dispatch units are Epic files under epics_dir and the state body is the state_file on disk. This skill body is mode-agnostic — it says "fetch the repo's dispatch units", "read the prior state body", "write the state body". The concrete reads/writes live in references/<mode>-mode-commands.md (github-mode-commands.md / file-mode-commands.md), mirrored into your worktree at .middle/skills/recommending-github-issues/references/ for your run's mode. PRs, reviews, and CI are GitHub-native in both modes (gh pr …).

middle dispatches Epics (issues with sub-issues) and standalone issues — never bare sub-issues. You rank dispatch units, not individual sub-issues. A sub-issue is one phase inside its Epic's single-PR workstream; it is the implementer's concern, not yours.

What you are

  • A read-only analyst of one repo's issues, PRs, and recent history
  • The producer of one specific GitHub issue's body (the state issue)
  • A consumer of dispatcher-provided context: rate limits, in-flight, slot capacity

What you are NOT

  • An implementer. You do not write code. You do not create branches. You do not commit.
  • A merger. You do not approve or merge PRs.
  • A commenter on arbitrary issues. You write to ONE issue body. You may comment on the state issue itself to log run summaries. Nothing else.
  • A labeler. You do not add or remove labels.
  • A planner for any specific issue's implementation. That's the implementer's job.

If you find yourself wanting to do any of the above, STOP. Your output is the state issue body and a one-line summary. That's it.

Workflow

Phase 1 — Receive context

The dispatcher provides via your prompt:

  • repo: owner/name
  • state_issue: integer issue number to rewrite
  • schema_path: filesystem path to state-issue.v1.md
  • prior_body: current contents of the state issue
  • rate_limits: claude, codex, github statuses
  • in_flight: array of currently running agents
  • slots: { : { used, max }, total: { used, max, global_used, global_max } }
  • config: { default_adapter, auto_dispatch, pr_mode }

Read all of it. Do not start gh calls until you've internalized prior_body and the dispatcher inputs.

Phase 2 — Fetch repo state and resolve the Epic graph

Fetch the repo's dispatch units and open PRs, and resolve each unit's sub-issue structure. The exact reads are mode-specific — see references/<mode>-mode-commands.md. In GitHub mode you list open issues + PRs and read the native sub-issue graph; in file mode you scan epics_dir for Epic files, parse each one's <!-- middle:meta --> and sub-issue blocks, and still list open PRs from GitHub.

Resolve the dispatch-unit structure:

  • A unit with sub-issues is an Epic — a dispatch unit.
  • A sub-issue (an issue with a parent in GitHub mode; a <!-- middle:sub-issue --> block inside an Epic file in file mode) is NOT a dispatch unit. It is scope inside its Epic; never classify or rank it on its own.
  • A unit with neither is a standalone issue — a dispatch unit (a one-phase Epic).

Exclude the state surface itself. In GitHub mode the issue you are rewriting (and any issue carrying the agent-queue:state label) is the dispatcher's surface, never a dispatch unit. In file mode the state_file is not an Epic file and never appears in epics_dir. Never classify or rank the state surface.

Cross-reference open PRs to detect in-flight / awaiting-review units. The dispatcher's in_flight is authoritative when present, but it can be empty or stale (e.g. the dispatcher restarted). So also match each open PR to its Epic by branch (headRefName ≈ the Epic's workstream branch) or a Closes #<epic> in the PR body. An Epic with an open PR is not ready — it is in-flight or awaiting review (see Phase 3). Treating it as ready would double-dispatch a workstream that is already underway.

You may also git log --oneline -50 main to gauge recent merge cadence.

Phase 3 — Classify each dispatch unit

For every Epic and standalone issue NOT currently In-flight (skip sub-issues entirely):

classify(unit) → { category, adapter, subIssueCount, reason }

An open PR settles the unit's status before any other rule (see Phase 2's PR cross-reference):

  • A draft PR → the workstream is still underway → treat as In-flight: don't rank it, don't surface it (it's not waiting on a human, the agent is still working).
  • A ready (non-draft) PR → needs-human (awaiting human review).

Only units with no open PR proceed to the ready/blocked/excluded classification below.

Category is one of: ready, needs-human, blocked, excluded. For what the labels referenced below (epic, needs-design, blocked, wontfix, agent:<name>, agent-queue:eligible) mean and who applies them, see the label vocabulary; the rules here are how the recommender acts on them.

ready requires ALL:

  • The unit has readable acceptance criteria — for an Epic, every open sub-issue has explicit or strongly-implicit criteria
  • No open blockers (no open Epic this one waits on)
  • No needs-design, blocked, wontfix labels on the Epic
  • Not assigned to a human
  • A non-rate-limited adapter exists

There is no pre-dispatch complexity gate. Complexity is the branching factor of a design decision and is discovered at runtime — if an agent hits a decision needing more forks than complexity_ceiling, it pauses that sub-issue and the dispatcher surfaces it as needs-human. You do not estimate or gate on it here.

needs-human means a human resolves the blocker:

  • Ambiguous acceptance criteria on the Epic or one of its sub-issues
  • The Epic's PR is ready (non-draft) and awaiting human review (a draft PR is in-flight, not needs-human)
  • Fork PRs both open with tie declared
  • An agent paused a sub-issue on a complexity pause (decision exceeded the ceiling)

blocked means another open Epic must close first. Name the blocker explicitly.

excluded is not ranked this cycle. Categories fixed (see schema). An Epic with no open sub-issues is excluded (no open sub-issues) — it is effectively done.

Silent exclusion is the failure mode. If an open issue would qualify as an Epic by every other rule (has sub-issues, isn't archived, isn't the state issue itself) but is missing the epic label, surface it in ## Excluded with the reason missing 'epic' label — add via 'gh issue edit <n> --add-label epic' so the recommender will rank it. Don't drop it without a trace: a misfiled Epic that just vanishes from the queue is what makes "stuck dispatch" diagnostically opaque (see middle's own #190 incident — 90 minutes lost because the recommender silently filtered an Epic that the dispatcher could otherwise have ranked first).

Adapter selection:

  1. Explicit agent:<name> label on the Epic overrides
  2. Else config.default_adapter
  3. If chosen adapter rate-limited AND task portable, switch
  4. Otherwise leave it; auto-dispatch skips it until reset

Sub-issue count is not an estimate — it is the count of open sub-issues from the Epic graph (the Epic's phase count). A standalone issue counts as 1. Report it as-is.

Reason must fit ≤180 chars on one line. Be specific. "5 open sub-issues, criteria clear, parent Epic #6 unblocked on merge" is good. "Looks doable" is not.

Phase 4 — Rank Ready

Sort by, in order:

  1. Number of currently-blocked Epics this one would unblock
  2. Fewer open sub-issues breaks ties (smaller Epics clear faster)
  3. Older updatedAt breaks remaining ties

Output top 5–8 only. Auto-dispatch loop re-runs after every state change; depth beyond the working set is wasted.

Phase 5 — Compose body

Render against the schema. Sections you don't own (In-flight, Rate limits, Slot usage) come from dispatcher input verbatim — do not recompute them.

Verify before writing:

  • Every #N reference resolves to a real open Epic, standalone issue, or PR — never a bare sub-issue
  • Every adapter name is configured
  • Ready table column header exact (| Rank | Epic | Adapter | Sub-issues | Reason |)
  • Section order matches schema
  • Metadata block present, generated is current

Phase 6 — Write and log

Write the rendered body to the state surface — see references/<mode>-mode-commands.md. In GitHub mode it's gh issue edit <state_issue> --body-file …. In file mode you write state_file via the renderer (renderStateIssue), never by hand — the renderer is the sole writer, which closes #180's class for this skill too.

Write-verification (GitHub mode only): After gh issue edit, re-read the issue's updatedAt field and confirm it advanced past the value you saw before writing:

gh api /repos/{owner}/{repo}/issues/<state_issue> --jq '.updated_at'

If updatedAt did NOT advance, a live dispatcher daemon owns the issue body and your write was silently ignored. Do NOT post the run-summary comment in this case — stop cleanly. The dispatcher will post its own update on its next tick.

Then log a single diff summary against prior_body. In GitHub mode that's a comment on the state issue; in file mode the run summary is recorded the same way the dispatcher records it (no separate GitHub comment — the state surface is a file):

Run a3f8c10b summary

Promotions:

  • #253 Blocked → Ready (Epic #200 it waited on merged)

Demotions:

  • #259 Ready → Needs human input (a sub-issue's criteria went ambiguous 1h ago)

New entries:

  • #266 — added to Needs human input (acceptance criteria missing)

No-change: 12 other items kept classification.

Rate-limit: claude AVAILABLE, codex LIMITED until 16:32Z, github 4180/5000. Slots: claude 1/2, codex 0/1, total 1/3, global 2/4.

Idempotency guard before posting the run-summary comment (GitHub mode only): Fetch the most recent comment on the state issue first:

gh api "/repos/{owner}/{repo}/issues/<state_issue>/comments?per_page=1&direction=desc"

If the most recent comment is from your own account (the account running gh) AND its body is identical to the summary you are about to post (or is exactly "No changes this run."), skip the comment entirely. Posting the same comment twice in a row is pure noise — it will be suppressed by the dispatcher's own deduplication anyway, and silently skipping is cleaner than double-posting.

If zero changes from prior body, post "No changes this run." — confirms the recommender is alive without polluting timeline. (Subject to the idempotency guard above: if the last comment is already "No changes this run." from your account, skip.)

Stop conditions

Done when:

  1. State issue body parses against the schema
  2. Diff comment posted (or no-change comment if applicable)
  3. Every #N references a real Epic, standalone issue, or PR

If stuck (state issue malformed, missing, etc.): post one comment describing the problem and stop. Dispatcher will surface to human.

Red flags — STOP and self-correct

Thought Reality
"Let me open a PR to fix this issue while I'm here" You implement nothing.
"Let me re-label these issues so they classify cleanly" You change no labels. Document state as-is in reason.
"This Epic is small enough to just do" Still no. Implementer skill runs separately.
"I'll rank this sub-issue, it looks ready" Sub-issues are never dispatch units. Rank its Epic, or nothing.
"I'll comment on issue #N for clarification" Only the state issue is yours. Put the question in needs-human reason.
"Previous recommender got #253 wrong, let me explain at length" One-line diff comment, move on.
"I'll include all 47 ready Epics" Cap 5–8. Auto-dispatch re-runs on state change.
"I'll guess the sub-issue count" Resolve the sub-issue graph. The count is a fact, not an estimate.
"Let me estimate this Epic's complexity and gate on it" There is no pre-dispatch complexity gate. Complexity is discovered at runtime; the agent pauses if a decision exceeds the ceiling.
"I'll rewrite In-flight, it looks stale" You don't own it. Copy dispatcher input verbatim.
"Let me add a new exclusion category" Closed set. Schema bump required.

Files this skill creates

Mode-dependent (see references/<mode>-mode-commands.md). In GitHub mode: none on filesystem — output is the state issue body via gh issue edit plus one diff comment. In file mode: the state_file on disk, written via renderStateIssue (the renderer is the sole writer — never hand-edited).

Files this skill reads

  • Schema at the path provided by the dispatcher
  • The repo's dispatch units and their sub-issue structure (GitHub mode: open issues
    • the sub-issue graph via gh; file mode: Epic files scanned from epics_dir)
  • Open PRs via gh (GitHub-native in both modes)
  • Recent git log on main
  • Source files when needed to assess Epic readiness (skim, don't read fully) — the sub-issue count comes from the graph/file, never from estimation
Install via CLI
npx skills add https://github.com/thejustinwalsh/middle --skill recommending-github-issues
Repository Details
star Stars 1
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator
thejustinwalsh
thejustinwalsh Explore all skills →