Issues¶
Bugs and work to do are tracked as issues. This page is for three readers: a player who found a bug, a maintainer or agent who writes a task, and the coordinator who triages and moves issues along. Labels, forms and the rules for agents all come from files in the repository, so this page and those files change together.
How to report¶
Start from a form; blank issues are off.
- Bug report (
.github/ISSUE_TEMPLATE/bug.yml) is for players. It asks for the Coney version or commit, your system (operating system, graphics card, controller), how you started the game (the story,--play-level, a debug-menu jump or Rumble), the mission and checkpoint, numbered steps to reproduce, the position and heading from the debug menu if you know them, what happens, what the original game does (optional, but it tells us whether Coney differs from it), and how often it happens. The exact steps matter most: a bug that can be repeated is a bug that can get a regression test (Testing). - Task (
.github/ISSUE_TEMPLATE/task.yml) is for maintainers and agents. It asks for a summary that opens with the problem (Writing an issue a newcomer can read), the area, the research page that covers it, what "done" means (the acceptance test Testing defines for this kind of work), a fix list or steps, and the issues it is blocked by (#12).
Writing an issue a newcomer can read¶
An issue is read by someone with no other context: a player, a contributor on their first day, an agent briefed with only "issue #N". Write it so that the first screenful answers what is wrong or missing, and what is the state now.
- Problem first. The first sentence of the Summary (or the "What happens" of a bug) says in plain words what is
wrong or missing for the player or the developer. The current state comes second. Never start from the pull
request that left the work ("Pull request #55 adds ..."): the issue is about the problem, and that pull request is
provenance. It goes on the
From:line, which names where the issue came from and nothing more. - State the status as it is. When the text names a pull request or another issue, check it with
gh pr view Norgh issue view Nand say what it is: merged, open (not merged yet), closed without merging. Write "merged", never "(open)" for a pull request that has landed, and tick the checklist item it finished. An issue is usually filed while the pull request it came from is still open, so that wording goes stale when it merges: describe what the repository does and lacks rather than only what a pull request will do, and the coordinator fixes the statuses of the issues that cite a pull request when it merges (Merging safely). - Link every page by a relative link. A research page or guide is written as a Markdown link that works from an
issue page, with the path in the repository as its text:
[docs/guides/workspace.md](../blob/main/docs/guides/workspace.md), and#anchorafter the path when a section is meant (check the heading's{#id}exists). Check the page exists onmain(git cat-file -e origin/main:<path>); a page or section that exists only on an unreleased branch is named in plain text with a note that it is not onmainyet. Source files can be linked the same way. GitHub's file view opens the page at the top for an{#id}anchor, so name the section in words too. Never write the repository's or the documentation site's full address anywhere (AGENTS.md). - Make each checklist item a step someone can verify. "Windows 10 extraction route" becomes "Document or automate
PCSX2's 7z extraction on Windows 10, whose
tarcannot unpack it". Say what to do, to what, and how you will know it is done; tick the item (- [x]) when it is done and name the pull request that did it. - Write numbers and names, not shorthand. An issue refers to
#123for issues and pull requests, to the research page with its section, and to the item numbers of the user's play-test by their text; a word like "the unit", "the stack" or a branch name alone means nothing to a newcomer.
Screenshots¶
Every issue has a Screenshots section, as every pull request does. It shows the problem: Coney at the place and
moment it goes wrong and, when there is a capture of it, the original from the same place. When there is nothing to
show (a sound, a research question, tooling), the section holds one line, Screenshots: none (<reason>), such as
Screenshots: none (sound only). The forms ask for it, and an issue filed from the command line has the same
### Screenshots section (Filing an issue).
Drag screenshots and short videos into the form's Screenshots box, and attach Coney's log, the text it prints in the
terminal you started it from, as a .txt file in the bug form's Log box (with no user name or email address in it:
Attached logs). From the command line,
gh issue create --attach ./shots/f.png (or gh issue edit N --attach ...) uploads the file, and a
 already in the body is pointed at the upload (gh 2.99 or later). Captures stay in scratch
and are deleted once uploaded.
Wording¶
Issues, pull requests and guides call whoever tested, was affected by a problem or created the issue or pull request
"the user" ("From: user playtest of the preview build, 2026-10-11", "the user says", "the user's screenshot"), and
whoever decides "the maintainer" or "the maintainers" ("a maintainer decides", "maintainer decisions"). Never "the
owner" for a person: whoever owns the repository on GitHub is one of the maintainers. The word "owner" stays for what
it means in the code and the research (an object's owner) and in GitHub's own fields. The rule is
repo.wording in the conventions file, and coney-tools repo audit finds the banned word.
What may be attached¶
Gameplay screenshots and short videos of Coney or of the original may be attached to issues. That is the only exception to No game data for issues: never attach asset files, save files, memory dumps, executables, BIOS images, disc images, extracted files or the game's text or script source. The bug form has a required checkbox that says so. A maintainer deletes an attachment that breaks the rule.
Attached logs¶
A log is public the moment it is attached, and a person can attach more than they meant: a path that names their
user folder, an email address, or a file that is not a log at all. So the hygiene workflow
(Workflows) downloads each .txt and .log attached to an issue's body whenever the
issue is opened or edited (and every open issue's in the weekly sweep) and scans it. Two checks of the audit report
what it finds, in the workflow's one comment on the issue:
issue.attachment-personal: a line shows a user folder (C:\Users\<name>\,/home/<name>/,/Users/<name>/) or an email address. Replace the name with~(~\Downloads\disc.iso), attach the new copy and delete the old link.issue.attachment-data: the file may be game data: it is not text, it is larger than a log should be, or enough of its lines look like the game's script source or data files. Delete it if it holds anything What may be attached rules out; a long ordinary log can stay.
The comment names the file and the line numbers, never the text, so it does not repeat what it warns about. The
patterns and limits (which links, how much is downloaded, the size and line thresholds) are hygiene.attachments in
.github/conventions.yml. The download is bounded (a size cap and a timeout), sent
without credentials and only ever held in memory: nothing attached is stored or run. The scan is a heuristic, so a
finding is a prompt to look, and a clean scan is not a promise. coney-tools repo audit --github --attachments runs
the same scan over every open issue. Removing the link from the body does not delete the file on GitHub; a person who
attached something private by mistake can ask a maintainer, who can ask GitHub to remove it.
Filing an issue¶
An issue filed from the command line skips the web form, so a guard runs before it is written. Nobody files, edits or
links an issue with a raw gh command: coney-tools issue is the only way, and it reads every rule from
.github/conventions.yml (issues) and the form files in .github/ISSUE_TEMPLATE/, so a change to a form or a rule
changes the command with it (Repository conventions). The commands are in
coney-tools.
- Draft.
coney-tools issue draft task(orbug) prints a blank body with the form's###headings in its order. A comment before each section carries the form's own description, whether the section is required, and the values it allows (the Area is one of theA-labels; a bug's dropdowns list theirs). TheFrom:line and theScreenshots: none (<reason>)convention are there too. Fill it in; the comments may stay, they are not shown and the issue is filed without them. - Check.
coney-tools issue check --title "..." --body FILE [--leftover] [--online]says what is wrong, one line per problem, and exits 1: headings at the wrong level (## Screenshots), with other names, extra, missing or out of order; a required section empty orx; an Area that is not an option; a docs or research page written as plain text instead of[docs/x.md](../blob/main/docs/x.md), or linked to a file or{#anchor}that is not onmain(read fromorigin/mainin your checkout, no network;git fetch originfirst); a Screenshots section with neither an uploaded image nor thenone (<reason>)line; a leftover without itsFrom:line; "owner" for the user; the repository's address. The form is the one the body's headings belong to (a "Bug:" title on a task-form body is checked as a task);--formoverrides it. The bug form's "How often does it happen?" also allowsUnknown. A page that exists only on a branch stays plain text with a note such as "not onmainyet". With--online, the words that call#Nopen, merged or closed are compared withgh(State the status as it is). - File.
coney-tools issue create --title "..." --body FILE [--label L ...] [--parent N] [--leftover] [--attach F]runs the same check and thengh issue createwith the form's labels and the Area'sA-label; every label is validated against the labels of the conventions, and a--labelof the type or status family replaces the form's (--label docsfor a documentation task).--parent Nthen links the new issue as a sub-issue of issue N, which is where a leftover goes (the issue its source pull request fixes).--dry-runchecks and prints the commands and writes nothing. - Change.
coney-tools issue edit N --body FILE [--title T]checks the new body the same way beforegh issue edit, andconey-tools issue link N --parent Pmakes an existing issue a sub-issue.
The coordinator, and an agent whose brief says to file, use these commands; any other agent hands the checked body file to the coordinator.
How the rules are enforced. Four layers, each for a way an issue could skip the form:
coney-tools issue draft,checkandcreate(above) put the check before the write.- A Claude Code hook in the repository,
.claude/hooks/issue_guard.py(registered in.claude/settings.json, so every contributor and agent session has it), refuses a rawgh issue create, agh issue editwith--body,--body-file,-bor-F, and agh apicall that creates an issue, changes its body or title, or links a sub-issue. It refuses only whereghstarts a command, so a command that merely mentions these (anecho, a commit message, the body of a here-document) passes; labels, titles, comments and closing stay withgh, and so does a command that names only another repository (-R owner/name,GH_REPO=, arepos/owner/namepath), which the hook tells from this one by the working directory'soriginremote. The refusal namesconey-tools issue draft,createandedit.coney-toolsitself runsghas a subprocess, which the hook never sees. - The
issue-checkworkflow (.github/workflows/issue-check.yml) runsconey-tools issue check --onlineon every issue opened or edited, so an issue filed on the website or through the API is covered too. When the check fails it posts one comment listing the problems, updates that comment on later edits, and deletes it once the issue passes. It only comments: it sets no labels and edits nothing. coney-tools repo audit --githubruns the same shape check over every open issue (issue.formin Audit checks), so an issue that was filed before these existed, or that slipped through, is flagged afterwards.
Labels¶
The label scheme follows rust-lang/rust's prefixes (A- for the area, P- for the priority, S- for the status),
so contributors who know that project recognise it. The labels live under labels in .github/conventions.yml
(Repository conventions), the one source of truth: edit that file, never the labels on
GitHub. The coordinator makes GitHub match it with coney-tools repo labels --apply; without --apply the command
prints the gh label create ... --force commands instead of running them.
The table is generated from labels.list (coney-tools repo render). parity is a difference from the original and
enhancement is beyond it, so it waits until the game is complete (Faithful first). The task form's
area list is the A- labels. The coordinator sets the priority, and an open issue carries one status label at a
time. A closed issue carries none (What a closed issue keeps).
| Family | Label | Meaning |
|---|---|---|
| Type | bug |
Something in Coney is broken |
parity |
Coney differs from the original game | |
task |
A piece of work for a maintainer or an agent | |
docs |
Documentation or research pages | |
enhancement |
Beyond the original; waits until the game is complete | |
| Area | A-combat |
Area: fighting, grabs, weapons, damage |
A-ai |
Area: AI humans, pathing, goals, cars driven by AI | |
A-missions |
Area: mission scripts, checkpoints, objectives | |
A-hud |
Area: HUD, menus, text, prompts | |
A-graphics |
Area: rendering, models, textures, animation, effects, lighting | |
A-audio |
Area: sound, music, speech | |
A-physics |
Area: collision, movement, cars, doors, world objects | |
A-camera |
Area: cameras and cutscenes | |
A-platform |
Area: window, input, gamepad, rumble, saves, loading, disc files | |
A-core |
Area: engine core: memory, chunks, tasks, scripting runtime | |
A-tools |
Area: coney-tools, build, CI, the sandbox | |
| Priority | P-high |
Priority: blocks play or other work |
P-medium |
Priority: next in line | |
P-low |
Priority: when there is room | |
| Status | S-needs-triage |
Status: not yet looked at |
S-ready-to-start |
Status: triaged and accepted; waiting for someone to pick it up | |
S-needs-info |
Status: waiting for the reporter | |
S-needs-research |
Status: the original must be studied first | |
S-in-progress |
Status: an agent or person is working on it | |
S-pending-review |
Status: a pull request is open | |
S-blocked |
Status: waiting for another issue or a decision | |
S-conflict |
Status: a pull request conflicts with main (set by CI) | |
S-failing |
Status: a pull request fails a required check (set by CI) | |
| GitHub's own | good first issue |
Good for newcomers |
help wanted |
Extra attention is needed | |
duplicate |
This issue already exists | |
wontfix |
This will not be worked on |
S-conflict and S-failing are for pull requests, not issues, and nobody sets them by hand: the hygiene workflow
puts them on an open pull request that conflicts with main or fails a required check, and takes them off when that
is fixed (Workflows).
Triage¶
The coordinator reads each new issue and:
- sets the type (
bugorparity, ortask,docs,enhancement), the area and the priority; - moves an accepted issue from
S-needs-triagetoS-ready-to-start: it is understood, has a priority and can be picked up as written. Otherwise it asks for what is missing with a comment and setsS-needs-info, setsS-needs-researchwhen the original must be studied first (Research workflow), or setsS-blockedwhen it waits for another issue or a decision; - closes a repeat as
duplicate(pointing at the first), and a request outside the project aswontfix; - turns a vague report into a task that an agent can take, with a research page link and an acceptance test.
Whoever picks up an S-ready-to-start issue moves it to S-in-progress. The audit counts every status except
S-needs-triage as triaged, so an S-ready-to-start issue already needs its priority label
(Repository conventions).
The life of an issue¶
Every open issue has exactly one status label, and the coordinator moves it:
| Status | Set when |
|---|---|
S-needs-triage |
The form sets it. |
S-ready-to-start |
Triage accepted the issue: type, area and priority are set and nothing blocks it. |
S-needs-info |
Triage found the report incomplete and asked the reporter. Back to triage when they answer. |
S-needs-research |
No research page describes the behaviour of the original. |
S-in-progress |
An agent or person picks up an S-ready-to-start issue and starts work. |
S-pending-review |
The coordinator opens the pull request, with Fixes #N in its body. |
S-blocked |
The issue waits for another issue (named in the task's "Blocked by") or a decision. |
An issue is closed automatically when its pull request is squash-merged into main, because the body says
Fixes #N (Pull requests). If the problem is still there after the merge, reopen the issue;
it gets S-needs-triage again (below). Close with a reason: completed, not planned (wontfix) or
duplicate.
What a closed issue keeps¶
A status describes work that is still open, so a closed issue never carries an S- label: the close reason
(completed, not planned, duplicate) is its final state. It keeps its type, area and priority labels, and
wontfix or duplicate when they apply, as history. A leftover status such as S-pending-review on a closed issue
only misleads whoever filters by it.
The issue-status workflow (.github/workflows/issue-status.yml) keeps this true without anyone remembering it:
- On close it removes every label that starts with
S-, whoever closed the issue and however (a mergedFixes #Npull request, the button,gh issue close). - On reopen it adds
S-needs-triage, so a reopened issue is looked at again, unless the issue already has anS-label (someone set the status as they reopened it).
It is the one workflow that writes to GitHub, and its token is limited to issues: write
(Workflows). If it ever fails, coney-tools repo audit --github reports the closed
issue under issue.closed-status and the coordinator removes the label by hand.
Working from an issue¶
A brief may be just "issue #N" (Briefing); the issue holds the rest.
- Read it, read only.
gh issue view N --commentsshows the form's answers and the comments. An agent usesghfor reading and for nothing else. - Download attachments with
curlinto your scratch folder (never into the repository); the link is in the issue text. Extract video frames withffmpeginto scratch, for exampleffmpeg -i clip.mp4 -vf fps=2 scratch/<name>/frame-%03d.png, and look at them. - Describe media, do not commit it. A research page or a test describes what the screenshot or video showed (the place, the heading, what moved) in words and numbers. The media stays on the issue; screenshots and recordings are deleted from scratch when done (Sharing one machine).
- Work the usual way: the research first when the status says so, then a failing test, then the fix
(Testing). The pull request description has a
Fixes #Nline in its Summary. - Agents write to GitHub only through
coney-tools issue. No comments, labels or pull requests, and an issue only withissue create,editorlink(Filing an issue), and only when the brief says to: the coordinator does the rest, from the same release queue as pull requests (How we work). An agent reports what it would say (the finding, the question for the reporter, the status change) in its message to the coordinator..claude/hooks/push_guard.pyrefuses a subagent'sghcommand that writes;coney-tools issuerunsghitself, so filing stays possible when the brief allows it. - HANDOFF lists only issue numbers in flight. The coordinator's state file names an issue by number and track, for example "#31, bug-doors, in progress"; the title and details stay on the issue, so there is one place to read.
Work found along the way becomes its own issue¶
A pull request merges only when every task in it is finished. Its Checklist lists what it finishes, each item ticked,
and CI's body check refuses a description with an unticked - [ ] item, the template's ... placeholder included
(coney-tools repo check-body, rules in pull_requests.checklist of .github/conventions.yml). There is no
"Remaining" list: work the pull request does not finish is not part of it and is not named in it, and a pull request
that finishes an issue says Fixes #N in its Summary. Pull requests merged before the rule's own pull request did
(2026-10-12 at 10:39 UTC) keep the Remaining lists they were written with; their items are open issues and go
through the normal queue, and the audit still holds those lists to the older rule (each unchecked item names an
issue as #N).
- The author finishes the task it was given. Work it finds on the way that is not part of the task (a bug, a gap, an
open question) is written up as the issue it would be: title, area, research page and "done" test, in the shape of
the task form, in the message it sends the coordinator. It follows
Writing an issue a newcomer can read: the problem first, not "the pull request leaves ..."; pages as
relative links; each checklist item a verifiable step. It starts from
coney-tools issue draft, saves the body to a file in its scratch folder and runsconey-tools issue check --leftoveron it until it passes (Filing an issue); it does not write to GitHub (Working from an issue), and the pull request neither lists nor links that work. - Work a player can see comes with a picture. For a visible bug, gap or stand-in, the author saves a capture of
Coney in its scratch folder's
shots/(and of the original from PCSX2 when it has one at the same place), names the files in the issue text it sends, and the coordinator files the issue with--attach. Pure research questions and tooling work need none. - The coordinator files the issue from that checked body with
coney-tools issue create --leftover(and its captures,--attach), naming where it came from on theFrom:line, and labels it at triage. It rewrites a draft that reads like a copy of the pull request, and when a pull request merges it updates the statuses in every issue that cites it. - A task a pull request cannot finish is split before the pull request is written. The part it can finish is the pull request; the rest is an issue (or a pull request of its own, opened when it can be finished). A task nobody will do is dropped, not left unticked.
- The state file keeps no work list. HANDOFF holds the live state (what runs where, what waits for CI) and issue numbers, never the work itself; work that exists only in HANDOFF is lost when the file is cleared.