Shoring Up a Fresh Fork: Issue Templates, Discussions, and the API-vs-Manual Line
Shoring Up a Fresh Fork: Issue Templates, Discussions, and the API-vs-Manual Line
The setup. A fresh GitHub fork — an actively-developed macOS port of a desktop-pet app — with no release artifact yet, a handful of inherited repository features, and a governance strategy (branching lanes, a triage lifecycle, a roadmap) still being codified. The goal: stand up the issue-reporting and discussion surfaces so contributors, when they arrive, land somewhere that reflects how this project actually works — rather than inheriting the fork-parent's habits.
This is the story of that cleanup: what we changed, why, and — the part that turned out to be most interesting — exactly where the boundary fell between what an automation can do over GitHub's API and what still requires a human at a web UI.
The starting problem: a fork inherits its parent's voice
A fork is born with its parent's settings. The inherited .github/ISSUE_TEMPLATE/ told the story of a different project:
- A Report Bug form whose version dropdown offered releases this fork has never published (
2.1.4,2.1.2,2.1.0,1.9.8,1.9.5), plus a "Steam Edition" option for a distribution channel that doesn't exist here. - A Feature Request form with no connection to this project's roadmap.
- A
config.ymlthat disabled blank issues and offered nothing else.
Meanwhile, the project's own governance documents (VERSIONING.md, ROADMAP.md) described a triage lifecycle, a set of branch lanes, and a labels table — none of which the inherited forms referenced. The repo's *behavior* (what users see when they click "New Issue") was out of sync with its *documented intent* (how issues actually get triaged and fixed). Classic small-fork drift.
Aligning features to the dev cycle
Issue templates. We rewrote bug_report.yml to match the report template in ROADMAP.md: summary, reproduction steps, expected-vs-actual, environment fields (macOS version, app version/build), an optional classification guess, and localization impact. Two new forms were added:
localization_report.yml— because localization is a ship blocker in this project (13 locales, JA/ZH priority). A translation gap deserves its own surfaced path, not burial inside a generic bug report.docs_report.yml— documentation defects have their own lane (docs/<topic>), so they get their own form and default label.
Labels. The repo came with GitHub's default set plus an upstream courtesy label (accessibility) — ten in all, half of which the workflow would never use. We trimmed to the labels the governance documents actually reference:
bug · hotfix · enhancement · localization · macos-version · docs · duplicate · needs-info
Six of those are the triage-lane labels from VERSIONING.md; needs-info is the "awaiting more info from the reporter" state from the triage lifecycle; duplicate survives purely because every issue tracker needs it. Everything else — invalid, wontfix, question, good first issue, help wanted, accessibility, documentation — went away. Not because they're bad labels, but because a label that maps to no workflow action is just decoration, and decoration is how trackers get confusing. Keep the labels the workflow actually uses; let the forms default-apply one label each and leave classification to the maintainer at triage.
Discussions. With question deleted from the label set and blank issues disabled, a contributor with a question had nowhere legitimate to go — they'd be forced into the bug form with required fields. So we enabled GitHub Discussions as the planned pressure valve: Q&A, help, and rough ideas live there; only confirmed defects and roadmap-backed items become issues. The route was made explicit everywhere a contributor would land — the "New Issue" page (contact_links in config.yml), the README (all three languages), CONTRIBUTING, and the governance docs.
Staying on the vision
The most deliberate part was deciding what *not* to do.
- Platform support. During the alignment, the question came up: should this fork support Intel Macs? External facts settled it in a hurry — macOS 27 (Golden Gate) is Apple-Silicon-only, Intel Macs end with macOS 26, Rosetta 2 is slated for removal in macOS 28. And the fork has no published release yet: dropping Intel now costs nothing, whereas carrying it would mean forever supporting an architecture with zero test hardware to verify against. The supported-platforms policy (Apple Silicon, M1+, macOS 26+) went into
VERSIONING.md, and the README's platform lines were aligned to it in all three languages. The issue form's chip field was removed — M-series generation genuinely doesn't help triage; Apple Silicon vs Intel was the only split that would, and Intel is gone.
- No promotion. The repo is deliberately *not* being promoted, so it stays hard to discover. That's a feature for this experiment, not a bug. It means the discussions surface will stay nearly empty, and the maintainer's periodic check is a thirty-second task — not a firehose. The point of standing up the pieces wasn't traffic; it was proving the workflow *can* scale when (if) the repo is ever promoted.
The API-vs-manual line
This was the most instructive part: what could be automated, and what had to be done by hand.
Via API (all scripted):
- List/create/rename/delete labels — a
PATCHto renamedocumentation → docs, aPOSTper new label, aDELETEper prune. (One gotcha:DELETEreturns204with an empty body; the parser has to handle that.) - Issue forms — the
.ymltemplates are just repo files, so they're plaingitcommits: rewrite, branch (docs/<topic>per the governance lane), push, and open a pull request viaPOST /pulls. Merging wasPUT /pulls/1/mergewithmerge_method: merge— a faithful--no-ffmerge with the merge SHA recorded, exactly as the backout strategy requires. - Reading state — label lists, open issues, PR status, star counts, even repo *search* (
/search/repositories) to diagnose our own discoverability.
Manual (no API, no workaround):
- Enabling Discussions. There is no API for it. One click in repo Settings, then a human decides the initial categories. (Configuring categories was deferred; the default works.)
- Discussions content itself. Discussions has a REST API of exactly zero — it's GraphQL-only, and even that is a read-you-can't-act-on seam. The automation cannot monitor or reply to discussion threads; only issues (convertible from a discussion in one click) are actionable by the process.
- Pinning the community announcement. The "how we triage" post is web-managed, not a repo file — a deliberate choice so it isn't version-controlled with the source.
The boundary lesson: labels, forms, docs, and merge workflow are all API-addressable; anything that lives in Discussions' own UI (categories, pinned posts, replies) is human-only. The clean partition is to route anything that needs action into issues, where the automated lifecycle can take over.
The fork-discoverability footnote
A curious coda: the repo doesn't show up for a generic search of its own name. That turned out to be GitHub's documented rule — forks are excluded from default repository search unless they out-star their parent. The fork has 1 star against its parent's 5, so it's invisible to the default search, full stop; age is irrelevant. It *is* findable with qualified search (fork:true, user:...), and the zero-star *non-fork* on page 2 of the results is consistent — the fork rule doesn't apply to non-forks. Nothing to fix; it's just how a deliberate, un-promoted fork behaves, and for this experiment it's exactly what we wanted.
*Written as part of an experiment in doing development with an AI assistant: what can be delegated end-to-end over the API, and which corners of GitHub still need a human.*