Skip to content

Latest commit

 

History

History
258 lines (211 loc) · 17.6 KB

good-first-issue.md

File metadata and controls

258 lines (211 loc) · 17.6 KB

Hive-curated good first issues

Hive can advertise a few of its open issues to new contributors — in particular contributors who bring their own coding agent (Claude Code, Codex, Copilot CLI, Bob, …). A periodic sweep (on by default; good_first_issue.enabled: false opts out) scores the open, unclaimed issues in each watched repository, applies GitHub's canonical good first issue and help wanted labels to the few that qualify, and leaves one welcome comment that explains how to land the fix through this hive's contributor relay without waiting on a human.

The label is advertising, not a hold. The issue stays in the hive's own queue. If a hive agent starts on it first, the sweep's label comes off and the welcome comment is edited to say so. If a contributor claims it through the relay first, hive leaves it to them — and counts the conversion.

Default on: the operator asked the hive to decide what qualifies without per-spoke YAML (#12185). The sweep is safe to leave on because it is capped (max_per_sweep, max_per_repo) and self-silencing — it does nothing until the hive has a public dashboard URL to link. Set good_first_issue.enabled: false to opt out.

Because the scanner usually claims a fresh issue within minutes, the sweep runs every 15 minutes and advertises issues as soon as they are an hour old. In a hive that drains its own queue quickly, few issues will ever qualify — that is expected; the label is for the ones the hive has not got to yet.

Attracting contributors with agents

The point of the label is reach. good first issue (exact spelling, colour 7057ff) plus help wanted are what GitHub's per-repository Contribute page (github.com/<owner>/<repo>/contribute) and the good-first-issue aggregators key on, so a hive-labelled issue shows up where people — and the people steering agents — look for a first task. The welcome comment then does the pitch, end to end:

  1. Claim through the relay. The comment deep-links this hive's relay page for this issue: <public_url>/contribute?issue=<owner>/<repo>%23<n>. The landing page recognises the parameter and greets the visitor with the issue they came from. Signing in with GitHub and claiming stamps the issue with hive:claim (see Contributor relay), which tells hive's own agents to leave it alone.
  2. Bring your own agent. The comment says explicitly that the repository is maintained with hive and welcomes agent-made contributions, and includes a one-line "paste this to your agent" prompt carrying everything the agent needs: repository, issue URL, relay claim URL, the DCO rule (git commit -s), the Fixes #<n> rule, and the repository's AGENTS.md when it has one.
  3. Open a PR that says Fixes #<n>. The closing keyword is how the contributor lane links the PR to the claim; without it the PR is an ordinary human PR and waits for a maintainer.
  4. Hive merges it. CI runs, the hive review runs, and a green PR from a claimed contributor whose tier is sufficient (require_tiers or registry_tiers_sufficient) merges on its own.

The comment also says that hive may pick the issue up itself if nobody claims it, and that claiming first prevents that. Note the interplay with the default actionable priority labels: good first issue already ranks a human-filed issue into tier 0 of kick lists (labels-and-control-signals.md), so an advertised issue is also one hive's own agents are likely to reach soon. The minimum age, the per-repo cap and the release-on-start rule keep that honest: hive never advertises something it is about to start, and it withdraws the advertisement the moment it does start.

What qualifies

The scorer (src/pkg/gfi) runs over the hive's last enumeration — the actionable issue list, which already excludes hard-suppressed, claimed and PR-covered items. For potentially eligible candidates carrying size or help wanted labels, the sweep reads GitHub's label timeline and checks permissions or organization membership to verify who applied those labels. Gates, in order; the first failure is the gfi skip reason=… logged:

Gate Skip reason
Pull request wearing the issue shape pull-request
Live claimant (hive:claim, assignee) claimed, assigned
An open PR already references the issue linked-pr
Hard-suppress label (needs-human, stage/045-needs-decision, a pre-045 stage/085-needs-decision, needs-spec, or the retired needs-direction / needs-decision) — always excluded, whatever exclude_labels says; needs-human next to approved-direction still excludes (#12680) hard-suppress:<label>
Any hive/claimed-by-* label (always excluded) excluded-label:<label>
Excluded label (kind/epic, epic, meta, meta-tracker, tracker, roadmap, security, blocked, hold, wontfix, duplicate, hive/in-progress, hive/claimed-by-*, the hive's own hive/advisory / hive/selfreport / hive/sentinel / hive/fleet reports, …) excluded-label:<label>
Operations tracking issue — the body carries <!-- hive:ops-tracking --> (the promote-stable workflow stamps its emergency follow-up issue with it), or the title/body uses soak-checklist phrasing ("24h after promotion", "post-hoc soak", "promote-stable") — a maintainer's checklist, not a code change (#13267) ops-tracking:marker, ops-tracking:soak-checklist
Time-gated — the body carries <!-- hive:recheck-after <RFC3339> -->, so nothing on it can be completed before the deadline time-gated
Title starts with Epic, Tracking, RFC, Proposal, Umbrella, Hive Advisory Report, … after leading emoji, [tags] and punctuation are stripped, or contains the word epic, rfc, umbrella or meta title:<word>
Title mentions security work (security, vulnerability, CVE-…, exploit, XSS, CSRF, RCE, injection) even without a security label title:security
Younger than min_age (default 1h) too-fresh
Body shorter than min_body_chars (80) or longer than max_body_chars (4000) body-too-short, body-too-long
Mentions more than max_packages (2) distinct source directories touches-N-packages
Fewer than min_score (2) positive signals score-N-below-M

Positive signals, each worth one point and each quoted in the comment as a reason ("hive picked this issue because: …"):

  • already sized small by a maintainer (size/XS, size/S, good-first-issue, beginner, easy, …) — the label's latest application must be by a human collaborator with admin, maintain, write or triage permissions, or a member of the owning organization (unless the repo's label-driven approval policy turns org-member trust off; its trusted bots also count)
  • a concrete, reproducible bug report (bug, kind/bug)
  • maintainers asked for help on it (help wanted) — the same label-provenance rule as the size signal
  • has clear steps or acceptance criteria (two or more headings/bullets/checkboxes, or phrases like "acceptance criteria", "steps to reproduce")
  • scoped to one or two packages (paths mentioned in the body)
  • short, focused description (body at most half of max_body_chars)

Size and help wanted labels last applied by bots (including hive accounts and github-actions[bot]) earn no point. Missing provenance or a failed timeline, permission or membership lookup also earns no point (#14835).

Candidates come from the last enumeration, which can be many minutes old, so immediately before labelling the sweep re-reads the issue's labels from GitHub, re-verifies label provenance and scores again. The welcome comment's reasons are recomputed from that fresh result. An issue that picked up blocked, a claim or a park label in between is skipped as stale:<reason> (a failed read skips as stale:label-read). #13267 was labelled from a snapshot taken before a contributor's agent had returned "blocked" on it.

Per pass, each repository gets at most max_per_sweep (2) new selections, and never more than max_per_repo (5) issues wearing the label at once — counting issues a maintainer labelled by hand. Within a repository the highest score wins, oldest first on ties. Issues with the good first issue label the hive did not apply are still given the welcome comment (once) so that their visitors get the relay pitch too.

The welcome comment

One comment per issue, found by its first-line marker <!-- hive:gfi -->. A later pass edits it in place when the rendered body changes; it never posts a second one. The default template (gfi.DefaultTemplate, a Go text/template over gfi.Vars):

<!-- hive:gfi -->
### 🌱 Good first issue — bring your own coding agent

Hi! This repository is maintained with [hive](<docs>/README.md) and welcomes contributions made **with your own AI coding agent** (Claude Code, Codex, Copilot CLI, Bob, …). <hive> picked this issue as a good place to start because:

- <reason 1>
- <reason 2>

**How to land it (no waiting on a human):**

1. **Claim it through the relay:** open <public_url>/contribute?issue=<owner>%2F<repo>%23<n> and sign in with GitHub. Claiming stamps the issue so hive leaves it to you.
2. **Fork or branch**, make the change with your agent, and sign each commit with DCO (`git commit -s`).
3. **Open a PR whose description says `Fixes #<n>`** — that closing keyword is how hive's contributor lane links the PR to your claim.
4. Hive runs CI and **merges green PRs from claimed contributors automatically**.

<details>
<summary>Paste this to your agent</summary>

```text
Fix #<n> in github.com/<owner>/<repo> (<issue url>). First open <claim url> and claim the issue through the hive relay (sign in with GitHub). Work on a fork/branch; keep the change scoped to the issue; sign every commit with DCO (git commit -s); open a PR whose description contains the line 'Fixes #<n>' so the hive's contributor lane can auto-merge it once CI is green. Read and follow <AGENTS.md url> before changing code.
```
</details>

If nobody claims it, hive may pick it up itself — claiming first prevents that. Docs: [contributor relay](<docs>/contributor-relay.md) · [contributor lane auto-merge](<docs>/hive-merge.md#contributor-lane-auto-merge) · [trust tiers](<docs>/contributor-trust-and-roles.md) · [AGENTS.md](<AGENTS.md url>).

Template variables: .Repo, .Number, .Title, .IssueURL, .ClaimURL, .RelayURL, .Docs, .Hive, .Reasons / .TopReasons (first two), .AgentsMDURL (empty when the repository has no AGENTS.md), .AgentPrompt and .Marker. An override set in good_first_issue.comment_template must contain {{ .Marker }} (or the literal marker); otherwise the config is rejected, because without the marker the sweep could not find its own comment.

Release when hive takes the issue

Whenever a hive agent's own request shows it working an advertised issue — a claim, comment, label or PR request through the relay, or an agent claim on the claims ledger — the good first issue label is removed (and help wanted too, when this process added it), and the comment is edited to:

<!-- hive:gfi superseded=true -->
~~Good first issue~~ — **superseded**: <hive> started working on this issue itself (claimed-by-hive, <RFC3339>), so the `good first issue` label has been removed. Thanks for looking! Other issues open to new contributors are listed on the relay page: <public_url>/contribute

A superseded marker is also how the sweep recognises, after a restart, an issue it must not re-advertise; reselect_cooldown (7d) covers the same within a process.

Withdraw when the issue was never newcomer work

A labelled issue the scorer now rejects for a waiting reason — a hard-suppress label, blocked, hold, an ops-tracking checklist or a recheck-after deadline — has its advertisement withdrawn on the next sweep whoever applied the labels: both good first issue and help wanted come off, and the welcome comment is edited in place (or, when the labels arrived without one, a single correction is posted):

<!-- hive:gfi superseded=true -->
~~Good first issue~~ — **withdrawn** by <hive>: this issue is not open to new contributors — it carries the `blocked` label, so it cannot be worked right now. The `good first issue` and `help wanted` labels have been removed (<RFC3339>). Sorry for the noise! Issues open to new contributors are listed on the relay page: <public_url>/contribute

Softer rejections (too fresh, low score, an epic a maintainer labelled by hand) leave a hand-applied label alone. Issues the enumeration filtered out (blocked, held, parked) that still wear the label are included in the pass for this purpose only. The event logs as gfi withdraw issue=… reason=… and audits as gfi-withdraw.

Metrics

Structured logs: gfi select issue=… score=… reasons=… claim_url=…, gfi skip issue=… reason=… (debug), gfi release issue=… reason=claimed-by-hive, gfi converted issue=… contributor=… kind=contributor|human (a claim through the relay on a labelled issue), and a per-pass gfi sweep scanned=… selected=…. The same events land on the audit log as gfi-select, gfi-release, gfi-withdraw and gfi-convert. Since-start counters (selected, released, converted, withdrawn) appear on Settings → Features → Good first issue curation and in the Features GET as goodFirstIssueStats. The dashboard marks labelled issues with a 🌱 pill in the project card listing.

Configuration

good_first_issue: in hive.yaml. Every scalar key below is also on Settings → Features → Good first issue curation, and a saved change applies on the next pass; exclude_labels and repos are YAML-only.

Key Default Meaning
enabled true Set false to opt out. Requires dashboard.public_url or a hub-assigned dashboard URL; otherwise the sweep logs gfi skip reason=no-public-url and does nothing.
interval 15m Sweep cadence (over the last enumeration; costs GitHub calls for label-provenance checks on potentially eligible candidates and for the issues it labels). The labelled set used for release-on-claim is refreshed every cycle regardless.
max_per_repo 5 Issues wearing the label per repository at once.
max_per_sweep 2 New selections per repository per pass.
min_age 1h Minimum issue age (long enough that a reporter has finished editing).
min_body_chars / max_body_chars 80 / 4000 Body length bounds.
max_packages 2 Distinct source directories an issue may mention.
min_score 2 Positive signals required.
reselect_cooldown 168h A released issue stays out this long.
exclude_labels built-in list Replaces the built-in exclusions; hard-suppress labels (needs-human, stage/045-needs-decision, stage/085-needs-decision, needs-spec, retired spellings) and hive/claimed-by-* are always excluded.
repos all watched Restrict to these owner/repo slugs.
docs_url hive's src/docs on v5 Base URL the comment links docs under.
comment_template gfi.DefaultTemplate Override; must contain {{ .Marker }}.

Welcoming first-time contributors

The same invitation reaches people who skip the issue and open a PR directly. When a pull request is opened by a human whose GitHub author_association is FIRST_TIME_CONTRIBUTOR or FIRST_TIMER, the hive leaves one comment (marker <!-- hive:first-timer-welcome -->) on it that thanks them and explains they can run their own hive — we call them clankers — and let it do the contribution loop (claim, PR, review fixes, rebase) for them. The comment lists the concrete advantages: a contributor profile (/contribute/dossier/<login>) and leaderboard place (/contribute/leaderboard) on this hive's dashboard, trust-tier progression (newcomer → contributor → trusted) that unlocks contributor-lane auto-merge, the hive handling failing checks, reviewer asks, rebases and PR adoption while they are offline, and faster turnaround. It links the /contribute landing page, contributor-relay.md and contributor-trust-and-roles.md.

Rules, mirroring the good-first-issue comment:

  • Once per author per repository. The sweeper remembers who it welcomed, and after a restart it looks for the marker on the author's earlier PRs in the repo before posting again.
  • Humans only. Bots, PRs carrying author/clanker or author/hive, PRs the hive attributes to itself, drafts and logins already known as relay contributors (or with a trust tier) are skipped.
  • Silent without a public dashboard URL — the links need dashboard.public_url or a hub-assigned URL.
  • Every post is audited as first-timer-welcome.
Key (governor.first_timer_welcome.*) Default Meaning
enabled true Set false to opt out.
template_path firsttimer.DefaultTemplate Path to a text/template file over firsttimer.Vars; must contain {{ .Marker }}.

Related