Managing the Issue Tracker
This guide describes how Spec Kitty structures its GitHub issue tracker so the work graph stays honest, machine-navigable, and free of the drift that hand-maintained checklists cause. It is aimed at maintainers and agents doing tracker hygiene — parenting issues, wiring dependencies, and pruning stale rollups.
The rules exist because a misleading work graph is expensive: a real defect parented under a release rollup is hidden when that rollup is closed, and a dependency captured only as prose is invisible to every tool that reads the tracker.
Functional epics versus meta-trackers
The tracker has two distinct kinds of parent issue. Telling them apart is the first decision in any hygiene pass.
- A functional epic is a real domain or slice of work. It owns
native GitHub sub-issue children, carries the
epiclabel, and has a body that describes the work (see Epic bodies). - A meta-tracker is a convenience rollup — a release go/no-go list, a
stabilization checklist, or a dashboard view. It carries the
meta-trackerlabel, its body is a checklist that references issues owned elsewhere, and it has no native children of its own.
The distinguishing test is native children, not the title. An issue titled
Umbrella: … that owns native sub-issues is a functional epic; an issue that
holds only a body checklist of items homed under other epics is a
meta-tracker. Judge by the sub-issue graph, never by the name.
Parent functional issues under functional epics only. Never parent a real work item under a meta-tracker.
Parent and child are native sub-issue links
Express the parent/child relationship as a native GitHub sub-issue link, not
as a ## Children checklist in the parent's body. Body checklists duplicate the
relationship and silently drift out of sync.
Create a link (REST — sub_issue_id is the child's numeric database id, not
its issue number):
CID=$(gh api repos/OWNER/REPO/issues/<CHILD> --jq .id)
gh api --method POST repos/OWNER/REPO/issues/<PARENT>/sub_issues -F sub_issue_id="$CID"
Or via GraphQL (node ids, not numbers):
PID=$(gh api repos/OWNER/REPO/issues/<PARENT> --jq .node_id)
CID=$(gh api repos/OWNER/REPO/issues/<CHILD> --jq .node_id)
gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -f p="$PID" -f c="$CID"
List a parent's children with gh api repos/OWNER/REPO/issues/<PARENT>/sub_issues.
Single-parent constraint. GitHub allows an issue only one parent; a second link attempt returns HTTP 422. To reparent, remove the old link first, then add the new one:
gh api graphql -f query='mutation($p:ID!,$c:ID!){removeSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -f p="$OLD_PARENT_NODE" -f c="$CHILD_NODE"
gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -f p="$NEW_PARENT_NODE" -f c="$CHILD_NODE"
Execution order is blocked_by, not prose
Encode sequencing as native blocks / blocked_by dependencies between
children, not as a #A → #B → #C sequence in the epic body. An epic body
should carry no ordering prose that a tool cannot read.
# REST — issue_id is the BLOCKING issue's numeric database id
gh api --method POST repos/OWNER/REPO/issues/<BLOCKED>/dependencies/blocked_by -F issue_id=<BLOCKER_DB_ID>
# GraphQL — issueId is the BLOCKED issue's node id
gh api graphql -f query='mutation($b:ID!,$k:ID!){addBlockedBy(input:{issueId:$b,blockingIssueId:$k}){issue{number}}}' -f b="$BLOCKED_NODE" -f k="$BLOCKER_NODE"
For a strangler sequence of children, wire each step blocked_by its
predecessor so the ready-to-start item is always unambiguous.
Epic bodies describe work, not children
An epic body is a functional description, not a child enumeration. Every epic body must convey:
- Why — the problem or motivation.
- For whom — which users or roles benefit.
- Intended effect — the outcome once the epic is done.
Preserve substantive rationale; drop mechanical checklists (the sub-issue graph already records the children).
Meta-tracker lifecycle
- Label a meta-tracker
meta-tracker, neverepic. - Its body checklist is by design. Do not audit a meta-tracker for "no in-body children" and do not native-link its checklist entries — the real issues live under their own epics.
- Close a meta-tracker once it holds zero native sub-issues. Its purpose is spent when the work it references is tracked under proper epics. Close it with an audit comment, and explicitly note any residual free-text checklist items so nothing silently vanishes.
Filing children from an epic
When you decompose an epic into issues:
- Match the epic's own stated breakdown — file exactly the slices the body names, in the order it names them. Do not invent scope the epic did not specify; flag anything too vague to file cleanly instead of guessing.
- Ground each slice in the real code first. If a named slice already shipped, record that with evidence rather than filing a phantom "to-do" ticket that misrepresents completed work as pending.
Triaging issues: type, severity, and release-blocking bugs
Every open issue carries two orthogonal classifications a triage pass must get
right: what kind of thing it is (native type) and how urgent it is
(priority). They are set independently and mean different things. A third,
independent axis — which code domain it lives in — is carried by the
domain:* labels; assign it from
Domain labels rather
than guessing a surface, since that section keys each label to the enforced
Modularity SSOT.
Native type is the kind carrier
Set the GitHub native issue type — Task, Bug, or Feature (the tokens
are case-sensitive: gh issue edit <N> --type Bug). Feature here is GitHub's
native issue-type name, not Spec Kitty terminology; the domain object is always
a Mission. The type is the sole kind
carrier; the old bug label was retired repo-wide and must not be reintroduced.
A capability request wearing a bug title is a Feature (retype it, don't leave
it as a Bug). The enhancement label and the whole type:* label family are
retired for the same reason; the issue templates set the native type directly.
Priority, and what makes a P0
Priority is a label (priority:P0 … priority:P3). priority:P0 on a Bug
means a release-blocking defect — and, per
ADR 2026-07-17-1,
that has teeth: an open P0 bug is expected to red mainline, mainline CI is the
authoritative release gate, and red CI means no release.
Because a P0 bug halts the release train, calibrate it deliberately. A bug is P0 if it trips any of:
- Data/state corruption or loss — overwrites recorded results, poisons/drops events, wrong-authority or split-brain committed writes.
- Silent wrong result / false success — a command reports success but skips or loses work (especially damning under the honesty ADR).
- Core workflow broken with no workaround — cannot implement / review / merge / accept / commit a mission; a deadlock or hang with no escape hatch.
- Blocks release directly, or a security exposure (credentials/secrets).
The escalation test — irreversibility × silence × false-success. The four triggers above share one shape: a defect is P0 when it can destroy or lie without telling the operator. Weigh three aggravating axes — is the damage irreversible (data/state loss, wedged mission with no escape), does it happen silently (no error surfaced), and does the tool claim success anyway (exit 0, "done", a fabricated record). A fault that scores on these axes is P0 even when it feels borderline; a fault that is bounded, loud, and recoverable is not.
Downgrade below P0 when a clear workaround exists, the fault is edge-case-only, cosmetic/UX, confined to a dev-assist command, or the exposure is bounded and sophistication-gated (a narrow race window, an attack needing privileged local position). Do not inflate P0 — crying wolf devalues the red-mainline signal. But do not under-call a genuine corruption, hang, or false-success: when a real destructive/silent/false-success defect is borderline, escalate. Red main is the safety net, not the failure (ADR 2026-07-17-1) — an honestly red mainline on a genuine P0 is the system working, not breaking. Escalation to P0 is an operator decision.
The operator-betrayal class is the archetypal P0. The most damning defects
this project ships are the ones where Spec Kitty fights its own operator — the
tool reports success while skipping, losing, or fabricating work: a --fix that
exits 0 repairing nothing, a merge that prints "cleaned up" while every delete
was refused, a close that commits a fabricated completion record, an init
that silently deletes user-authored missions, a finalize that wedges the
mission with no escape. These are almost always surfaced by running Spec Kitty on
Spec Kitty (label them catfooding), and under the honesty ADR they are
release-blocking by default: a tool the operator cannot trust to tell the truth
about its own actions is not shippable. Treat operator-betrayal defects as P0
unless a concrete argument downgrades them.
Priority on a non-bug means something else: a priority:P0 Feature/Task
is the priority of planned work, not a release-blocking defect — it does not
imply a red mainline.
A P0 bug should carry a failing reproduction test
Per the ADR, when you file or accept a P0 bug, land an issue-pinned red-first
reproduction test so CI honestly reflects the blocker instead of only asserting it.
Mark it @pytest.mark.p0_repro(issue=N), reproduce the defect through the
pre-existing entry point, and say in the docstring that it is an intentional red-first P0
reproduction. It must fail because of the product defect, not because of a test error: a
false red corrupts the signal it exists to give.
p0_repro tests run only in the nightly p0-repro lane. They never run in a PR's CI,
so PRs land on green while the nightly, and therefore the release, stays blocked until the
fix lands. The fix PR removes the marker. Do not use regression (per-PR guards for
bugs that are already fixed) or quarantine (built never to run) for an open P0.
Expensive QA and manual testing wait for green, and maintainers prioritize red recovery.
See the red-main policy.
What "good first issue" means here
good first issue is not a difficulty or seniority signal — this project's
contributor base is senior by default. It marks a good entry point into the
codebase: a self-contained issue that teaches a subsystem while carrying low
customer and blast-radius impact, so someone new to the repo can land it
without first absorbing deep cross-cutting context.
Reach for it when an issue is well-scoped, has a clear deliverable, and touches a
peripheral or tooling surface rather than a load-bearing one. One guardrail: if a
good first issue could still alter a blocking or shared gate (or otherwise
carry real blast radius despite being a good entry point), require maintainer
concurrence on the chosen approach before implementation — normal PR review
then covers the rest.
Running a triage pass over a batch of issues
A maintainer pass over a batch of open issues (by default those created in the last
7 days) is run by the issue-triage-pass procedure, which the issue-triage pack skill
starts. The orchestrator fixes the work list and the code baseline, dispatches triage
subagents that load the planner-priti profile, re-checks every issue before each write,
and puts the flagged items to the operator. The rules above are what each subagent
applies. Three points are easy to miss:
- Never set
priority:P0in a pass. Name the criterion the issue meets and flag it; escalation is the operator's decision. - Leave
status:*andtaken-by-humanalone, with one exception. They are leases and queue state, set by a human or by contributor tooling (see Issue workflow-state labels). The exception isstatus:triage: once an issue's triage decisions are applied, remove it, and add no other status label in its place (triage:*sub-labels stay). A flagged issue keepsstatus:triageuntil the operator's ruling is applied. Report any other status label that looks stale. - Check liveness against the canonical remote. Judge "still exists" or "already
shipped" against
mainofspec-kitty/spec-kitty, fetched now, not a fork'sorigin/main, which can lag far behind.
Milestones and release cycles
Milestones carry release scope; labels carry kind, priority, and domain. The
active cycle is 4.0.0, currently at the release-candidate stage on main.
The 4.0.0 milestone roadmap is the
authority for what each milestone means. In short:
- 4.0.0 release scope — the GA gate: issues that must close before a non-rc
4.0.0ships. Put an issue here only if it truly blocks GA. - CLI 4.x stable — stable 4.x work after the 4.0.0 launch. Post-4.0.0 deferrals go here.
- 4.x Work — the former
4.0.0milestone; now hosted-collaboration and product work beyond the stabilization release. Being on it does not make an issue a GA blocker.
The 3.x milestones are closed: 3.2.7 was the last 3.x release, and the 3.3.x milestone was retired on 2026-08-23 without shipping. An issue whose text says "deferred to 3.3.x" needs re-triage onto one of the milestones above; do not reopen or reuse the 3.x milestones.
Label taxonomy
Two orthogonal axes carry the load and are documented above: the native type
(Bug / Feature / Task — the sole kind carrier) and priority
(priority:P0…priority:P3). Labels never encode kind. The bug and
enhancement labels and the whole type:* label family (type:bug /
type:feature / type:chore / type:fix / type:finding / type:flake /
type:thesis) duplicated the native issue type as a parallel, drift-prone kind
carrier and are retired: the native GitHub type is the single canonical
authority for kind. These labels were deleted from the repository on 2026-10-19;
if one reappears, strip it and set the native type. The former type:decision
label is now triage:needs-decision (see
Triage-state labels).
Beyond type and
priority, labels fall into the families below. Apply as many as genuinely apply; a catfooding reliability Bug at
priority:P2 is a normal, well-triaged issue.
Classification labels — what kind of work it is
These describe the nature of the work independently of the subsystem. They are
orthogonal to native type (a catfooding issue is still a Bug, Feature, or Task).
catfooding— Doctrine Catfooding: dogfooding Spec Kitty's own quality/doctrine practices. Applied to defects and frictions surfaced by running Spec Kitty on Spec Kitty — a live mission session that exercises the specify → plan → tasks → implement → review → merge loop and reports where the tool fought its own operator. This is the dominant classifier for the post-convergence dogfooding backlog; expect clusters ofcatfoodingworkflow/git/usabilityissues that circle a single root cause.tidy-up— Campsite / declutter / degodding cleanup surfaced during other work (the boy-scout residue a mission couldn't fold in-line). The formercleanuplabel was merged into it.tech-debt— Accumulated lint, type-checking, static-analysis, and code-quality debt.design-spike— Foundational design work.research— Research / experiment validation work.
Domain labels — which code domain the work routes to
domain:* labels route an issue to the code domain it lives in. The domain
set is keyed to the Modularity SSOT — the enforced pair CI actually defends,
not this prose:
- Module inventory —
pyproject.toml[tool.hatch.build.targets.wheel].packages, guarded bytests/architectural/test_pyproject_shape.py. - Import direction — the
landscapefixture intests/architectural/conftest.py+tests/architectural/test_layer_rules.py, whose enforced chain iskernel <- charter <- {glossary, runtime, mission_runtime} <- specify_cli.
Every prose module map — this section,
docs/architecture/00_landscape/README.md,
docs/architecture/04_implementation_mapping — is a derived view; on any
conflict the enforced pair wins. Because the top specify_cli module is large, a
few domain:* labels route to its high-traffic sub-packages rather than to the
whole application layer.
Selection rule. Pick the domain:* whose code the fix will actually edit;
apply every domain that genuinely applies (a change spanning modules carries one
label per surface it really touches). A domain:* answers which module,
independently of native type, priority, and the cross-cutting concern labels
below.
| Label | Code domain (SSOT module / sub-package) |
|---|---|
domain:kernel |
src/kernel/ — zero-dependency foundation primitives (root layer) |
domain:charter |
src/charter/ — governance authority; includes the former doctrine system now absorbed at src/charter/offering/ |
domain:glossary |
src/glossary/ — terminology / semantic-integrity pipeline + DRG glossary bridge |
domain:runtime |
src/runtime/next/_internal_runtime/ — canonical mission control loop |
domain:mission-runtime |
src/mission_runtime/ — artifact-placement seam (PlacementSeam, resolver port, identity, lifecycle_phase) |
domain:cli |
src/specify_cli/cli/ — control-plane / CLI command surface |
domain:invocation |
src/specify_cli/invocation/ — dispatch & profile-invocation routing (Op lifecycle) |
domain:decisions |
src/specify_cli/decisions/ + cli/commands/decision.py — Decision Moment flow, models, emit |
domain:status |
src/specify_cli/status/ + lanes/ + coordination/ + workspace/ + state/ — status event-log & lane state machine; state/ is the project-scoped state-surface contract and doctor state-root checks |
domain:consolidation |
src/specify_cli/consolidation/ — lane consolidation, retention, preflight (formerly domain:merge) |
domain:skills |
src/specify_cli/skills/ — command/skill rendering, install, and the command-skills manifest |
domain:upgrade |
src/specify_cli/upgrade/ (incl. upgrade/migrations/) + cli/commands/upgrade.py — spec-kitty upgrade: version detection, migration registry/runner, redeploying generated commands and skills |
domain:review |
src/specify_cli/review/ + cli/commands/review/ — WP review cycle, verdicts, arbiter, the pre-review transition gate (gate_registry, pre_review_gate), and the spec-kitty review mission-review gates |
domain:acceptance |
src/specify_cli/acceptance/ — the accept gate: acceptance matrix, verify, summary_core path conventions |
domain:orchestrator |
src/specify_cli/orchestrator_api/ — the orchestrator-api external machine contract (JSON envelope + subcommands), documented in docs/api/orchestrator-api.md |
domain:contracts |
src/specify_cli/contracts/ + docs/contracts/ — payload / handoff-packet contract registry |
domain:tracker |
src/specify_cli/tracker/ — tracker provider integrations & connectors (spec_kitty_tracker consumer) |
domain:docs |
docs/ + scripts/docs/ + src/specify_cli/docs/ + cli/commands/docs.py — documentation content, docs build/freshness tooling, and the docs query retrieval index |
domain:ci |
GitHub Actions workflows (.github/workflows/) + scripts/ci/ — pipeline, gate selection, coverage aggregation |
domain:onboarding |
first-run setup spanning modules (init, charter, remote, first mission) |
domain:git |
src/specify_cli/git/ + src/kernel/git/ — git operations, worktree / topology helpers |
domain:agent-profiles |
src/charter/offering/agent_profiles/ + profile loading in specify_cli |
domain:schema-versioning |
src/specify_cli/schemas/ — event / payload schema versioning and spec-kitty-events consumption |
domain:status-api |
services/mission-status-api/ — the Mission Status Read/Write API service, its contract and its consumers (successor of the removed dashboard, #5530; formerly domain:dashboard) |
domain:ci,domain:docs,domain:onboardinganddomain:status-apiname a workflow surface or service outside the Python Modularity SSOT rather than a single package. They are still domain labels because they answer where the work lives for routing — unlike the concern labels below, which name a quality/experience axis that can attach to any domain.
Legacy labels — history only
legacy:sync (the local sync transport, deleted in The Convergence, ADR
2026-09-06-1-convergence-retirement-and-client-repo-inversion)
and legacy:hosted (the hosted Team Kitty clients saas_client/,
zeitgeist_client/ and auth/, removed in #6164; formerly domain:hosted) mark
closed issues for history. Never apply them; re-home any live issue to the
domain:* label whose code the fix edits. The earlier renames of the
non-namespaced labels (dashboard, saas, agent-profiles,
schema-versioning, git) are complete. A legacy:* label exists only for a
retired code area; retired process labels are deleted outright.
Cross-cutting concern labels — what quality/experience axis, not a code domain
These name a concern that spans modules; they are not domain:* labels and
do not route to one package. Apply them alongside a domain:* label.
reliability— Runtime reliability, resiliency, observability, incident prevention.usability— Ergonomics of any operator-facing surface: messages, flags, output, defaults.workflow— The mission loop itself (specify → plan → tasks → implement → review → consolidate) behaves wrongly or awkwardly.windows— Windows-specific issues (a platform axis, not a module).
Triage-state labels — transient hygiene state, not classification
These record where an issue sits in triage. Remove them once resolved; they are working state, not a permanent property.
triage:maybe-duplicate— Suspected duplicate pending confirmation (as opposed to the confirmedduplicate).triage:needs-revision— Scope/spec needs rework before action.triage:stale— Reproduce and close if no longer valid.triage:repro-needed— Reported against behavior that may already be fixed; needs live re-reproduction before implementing.triage:needs-investigation— Root cause is not yet adjudicated. The defect is real (often a confirmed red), but which layer owns it is unknown — e.g. a red test that could be a stale fixture/seed or a genuine product bug. Blocks final homing (parent/priority) until an investigation or debugger pass resolves the root-cause axis; remove it once the cause is known.triage:needs-decision— A human (operator or maintainer) must make a call before anyone implements; the body states the options. Formerlytype:decision.
Apply at most one triage:* label per triage cycle.
Provenance labels — who surfaced it
These record where an issue came from. Provenance is orthogonal to type, priority and domain.
from:ci— Filed by CI automation (scripts/ci/nightly_escalation.py). Code writes and searches for it; never remove it by hand.from:qa— Found by a human QA tester.from:dev— Found during development.from:squad— An adversarial-squad finding (theadversarial-squadskill).
PR-workflow labels — for pull requests, not issues
These live on pull requests, not issues.
ready-for-squad— the implementer has run the required tests and self-review; the PR is finished and independently testable. Do not apply it to a still-draft scope or to work whose tests have not run. (While the SkyKitty agent fleet was active this label also handed the PR to the fleet; the fleet is switched off.)pr:p0-repro— the PR lands the red-first reproduction test for a confirmed open P0Bug. Reference the issue withRefs #<n>, notCloses, and mark the test@pytest.mark.p0_repro(issue=<n>)(see A P0 bug should carry a failing reproduction test).pr:needs-refresh— PR branch drifted frommain; rebase/refresh before review or merge.pr:needs-revision— PR has unresolved review findings that must be addressed before it can merge.pr:kept-for-reference— Kept open for reference/history; not intended to merge as-is.pr:deferred— Held under a declared freeze; the label-gated CI jobs (ci-windows.yml,check-spec-kitty-events-alignment.yml,release-readiness.yml) are skipped until it is removed.pr:skip-ci— Intentionally skip CI for this PR (docs / manual review only).
Lifecycle & community labels
deferred— Work paused pending an activation trigger.feedback-welcome— Community feedback & brainstorming welcome; comment ideas/objections, no code needed.good first issue— a good entry point (see What "good first issue" means here); not a difficulty signal.release— Release engineering: tagging, publishing, release-pipeline integrity. Release and launch scope lives in milestones, not labels.duplicate— A confirmed duplicate; pair it with the native "Close as duplicate".invalid,wontfix,questionandhelp wantedare retired: use the native close reason "not planned", or atriage:*label.- Docs work is routed by
domain:docs(the formerdocumentationlabel was merged into it); doctrine prose is routed bydomain:charter.
Grouping labels (documented above)
epic and meta-tracker are covered in
Functional epics versus meta-trackers —
they are not free-floating classification labels and carry structural obligations
(native children vs. a body checklist).
Issue workflow-state labels
The issue-side status:* labels and taken-by-human are human workflow state:
| Label | Meaning |
|---|---|
status:triage |
New, untriaged work |
status:ready |
Triaged, de-duplicated, and resolvable without a pending decision; ready to pick up |
status:claimed |
Someone is actively working the issue. A lease: valid only with a live claim comment naming who is working it. Remove it when you stop |
status:blocked |
Blocked, with a Depends on: #<n> line naming the gating issue |
taken-by-human |
A human contributor owns the issue or its linked PR. Pair it with status:claimed when you begin |
Contributor tooling may also set these labels; treat a label it set like one a person set.
Label-driven fleet workflow
Status: the SkyKitty fleet is switched off (the
CI Fleet Verdictworkflow is disabled; last run 2026-09-26). The issue-sidestatus:*labels andtaken-by-humanstay in use as human workflow state (see Issue workflow-state labels). The PR-lane labels (squad:*,needs:implementer,ci:green/ci:red) were deleted on 2026-10-19 and would be recreated if the fleet is reactivated. The rest of this section is historical: it records how the fleet consumed the labels while active.
The SkyKitty agent fleet and its sibling programme repos run on a
label-driven queue: GitHub labels are the only admission control. Agents and humans
move work by changing labels, never by out-of-band assignment. This section documents the
queue that consumes the labels catalogued above. Source of truth: the planning repo's
agents/dispatcher.md, agents/groom.md, agents/merge.md, agents/review.md, and
PROGRAM.md.
The fleet scripts named below (bin/unblock.py, bin/take-next-ticket.sh, and
bin/dispatch.sh) live in the private EXPERIMENTAL-spec-kitty-planning repository,
not in this Spec Kitty checkout. They describe fleet behavior; contributors do not
need to run them to manage an issue.
The status:* lifecycle (issues)
This is the fleet's admission lifecycle, distinct from the transient triage:* hygiene
sub-states above (which flag why an issue sits in triage, not where it is in the queue).
| Label | Meaning | Who sets it |
|---|---|---|
status:triage |
New/untriaged work. The groom agent's queue. | Filing agents (from:review, from:squad, from:ci, …) always file into triage |
status:ready |
Valid, de-duplicated, resolvable without a reserved decision. The fleet's admission queue. | The groom agent promotes triage → ready. Review-agent findings are promoted by the controller, never by the review agent itself |
status:claimed |
An implementer is actively working this issue. Lease — valid only with a live claim comment naming the harness/model or dispatcher VM. | The dispatcher sets and reaps fleet claims. A human-driven contributor must add both status:claimed and taken-by-human when beginning work, then comment claimed by <harness>/<model> on <machine>. Never add a bare claim when nobody is working the issue: the dispatcher treats the label as occupied capacity |
status:blocked |
Genuinely blocked, with a Depends on: #<n> line naming the concrete gating issue. bin/unblock.py flips it back to status:ready automatically once all dependencies close. |
Groom/controller |
taken-by-human |
A human contributor (not a fleet VM) owns the linked PR. The only label that keeps the fleet's PR-routed fix and recovery lanes off human-driven work: those lanes select on the PR's own labels and never see the linked issue's status:claimed, so this — not status:claimed — is what holds a fleet implementer VM off your PR (PROGRAM.md §13). (Initial admission is separate: the dispatcher already skips issues that carry status:claimed.) Pair it with status:claimed on the issue when you begin. |
The human contributor; bin/take-next-ticket.sh sets it automatically for fleet-assisted human claims |
Issues with no status:* label and an open linked PR (a real closes # relationship)
are mid-negotiation, not unowned — the groom skips them deliberately.
Admission: from label to VM
- The dispatcher (
bin/dispatch.sh, deterministic, not an LLM) sweeps continuously. Each sweep lists open issues withstatus:readyand nostatus:claimed, ordered by priorityP0..P3, then repo order (PROGRAM.md§2), then issue number. - It skips issues labelled
status:blocked,epic, ortriage:needs-decision(formerlytype:decision; a reactivated dispatcher must match the new name) — a decision issue is never autonomous implementation work, even atstatus:ready; it waits on a resolving comment (docs/DECISION-ISSUES.md). - For each issue up to free implementer capacity, the dispatcher claims it: adds
status:claimed, commentsclaimed by dispatcher → sk-impl-<repo>-<n>, and boots a one-shot implementer VM. - Reaping: when an implementer VM exits with no linked PR, the dispatcher posts the last
40 log lines as
[dispatcher] implementer exited without a PR, removesstatus:claimed, and destroys the VM — the issue returns to thestatus:readyqueue and is re-claimed on a later sweep. A VM older than 6 hours with no PR is treated the same.
PR-lane labels (review and landing; historical, labels deleted)
PRs moved through their own label set, driven by the same dispatcher (see also the
ready-for-squad handshake under PR-workflow labels):
| Label | Meaning |
|---|---|
ready-for-squad |
PR is ready for adversarial review; dispatcher boots a squad VM (one iteration) |
squad:running |
Squad VM active (lease; stale-claim recovery requires confirming the VM is actually dead, never elapsed time alone) |
squad:passed / squad:majors |
Squad verdict. Two squad:majors rounds is the cap — the merge agent then decides merge-with-findings or sends it back; never a third squad |
squad:exhausted |
Two verdicts already posted (or the merge agent ruled out a further pass); merge proceeds on [ci] + controller evidence |
ci:green / ci:red |
Best-effort label mirror of the latest exact-head [ci] comment. The comment is authoritative; the label is never a gate on its own |
needs:implementer |
Any fix/rebase request that expects a new push. Mandatory on every such request — the dispatcher dispatches fix-round VMs when this label or squad:majors admits the PR to the fix lane; a request comment carrying neither is never seen |
See also
- The SkyKitty agent fleet — the fleet that acts on this repo, its
roles, and the
ready-for-squadhandshake these labels feed. - Contributing to Spec Kitty — pull-request and maintainer workflow.
- Keep main clean — branch and merge discipline.
- PR landing — the fork-PR landing runbook, incl. red classification.
- Red main and release readiness — what a red main means and the release gate.
- ADR 2026-07-17-1 — Red main is honest signal; CI is the release authority.