FILING_AN_ISSUE
Which parts, images and links to other issues does a filed issue carry?
Binds: process
This page states the cases in which somebody files an issue, and what the issue holds. It binds anybody who works here.
TAKING_AN_ISSUE.md covers how work on an issue starts and ends.
Future work lives in GitHub Issues
Send these to Issues, and never into docs/:
- Open questions
- Known gaps
- Ideas
- Anything that you defer
A “TBD”, a “later” that no decision backs, or an “open questions” section is future work in the wrong file. It is exactly the part that rots, because nobody reads a doc to find the backlog. If a doc makes you want to leave a placeholder, open an issue, and let the doc stay silent on it.
docs/DOCUMENTATION.md states how a page writes the future that a decision did
settle.
An issue holds work that waits. It gates no work that starts now. A small improvement needs no issue before somebody makes it. The sole branch commit carries the decision.
One writer is not a person. .github/workflows/file-testflight-feedback.yml
opens an issue for each piece of feedback that a tester sends, one issue per
submission, labelled source:testflight.
ios-app/docs/operations/TESTFLIGHT.md states what such an issue holds. Every
rule on this page covers it, the same as an issue that somebody typed.
When the work waits for a person, or nobody starts it today, open an issue. A
decision:* label names the kind of question that blocks it, and a needs:*
label names what the issue waits for. LABELLING_AN_ISSUE.md states each label.
So an issue records a wait, and never a permission.
An issue states a problem and an outcome, and its ideas bind nobody
An issue that a person or a session writes holds these parts, in this order:
- Problem. What goes wrong today, for whom, and when. State what it costs: a defect that a person meets, time that somebody spends, or a risk and its path to harm.
- Evidence. Each fact behind the problem. Give a path as
file:line, a command with its output, and an issue or a pull request by number. Write “not verified” beside a fact that nobody checked. - Outcome. The state of the world once the problem is gone. Write it in the words of the problem, and name no mechanism.
- Done when. A check that a fresh session can run at once, and that proves the outcome. A check that waits for later events is no check.
- Ideas (not binding). Each way to reach the outcome that the writer saw. This part is optional.
An idea binds nobody. The person who takes the issue reads the problem and the outcome first. That person then picks the means. They can take an idea, change it, or drop every idea.
A “Done when” that names an idea turns the idea into the outcome. “A command exists” is such a check. Write the check that the idea was meant to pass, such as “a colour change takes one step, and the tests pass”.
Never file an issue whose only work is to show that a fix worked. A “Done when” such as “three pull requests in a row show no denial” waits for events that nobody schedules. The issue sits in the queue until one session happens to see the result. If the friction returns, a session files it then, with the evidence. If it does not return, the issue costs a reader and proves nothing. Say in the pull request what the change does not show, and stop there.
A choice that its owner already made is a constraint, and not an idea.
WORKFLOW.md, section “Planning uses known decisions”, states such a choice.
Write it under the outcome, and name the comment or the commit where the owner
made it.
An issue that a tool opens follows the page of that tool. Such an issue
reports what happened, so it already states a problem.
tools/feedback-intake-for-github/docs/README.md states the issue of an app
report. The TestFlight page, which the paragraph “One writer is not a person”
names, states the issue of a TestFlight report.
An issue about cost names a harm that somebody met, or a path to harm
When nobody knows the cost yet, the problem says so. It names what somebody met, and the evidence that will show the cost, such as a count of cases or the time of one case. Nobody picks a means before that evidence arrives.
An issue about speed or memory names a slowdown or a failure that somebody met. A guess that a default of a dependency can be slow states no problem. A session that meets the slowdown files the issue then, with the evidence.
An issue does not test what public documentation states. Where the documentation of a dependency states a behaviour, read it. An experiment starts only after somebody meets a case that the documentation does not explain.
A risk with a path to harm needs no harm that somebody met. A security gap is such a risk, because the path already exists.
An issue carries its images as attachments
A plot, a screenshot or a recording that explains an issue goes on that issue. A
reader then sees the evidence where the decision happens. The section “Do not
publish work without a request” in AGENTS.md names the issues that a session
can attach to.
Attach each file with gh issue comment --attach. The body can reference a
local file with Markdown image syntax. GitHub CLI rewrites that path to the
attachment URL. Repeat --attach for each file. Give each image its alt text
after #.
gh issue comment <number> --body-file .scratch/analysis.md \
--attach '.scratch/plot.png#The weekly target on each day'
The upload rules of the section “Graphical changes carry visual evidence” in
WORKFLOW.md hold here too. Health data of a tester is personal data. A plot
from a rebuilt or a synthetic history says so in its caption and in the comment.
The comment names the commit and the script that made each file, so a reader can
make it again.
An issue links to another issue by a relation that GitHub records
A block between two issues is a dependency, not a label. GitHub records
blocked by and blocking on the issue itself. Set the relation there. The
search term -is:blocked then removes a gated issue from the queue. See “An
assignee means that work started” in TAKING_AN_ISSUE.md, which holds the full
query.
blocked by marks a separate issue that must land first. A needs:* label
covers the other kind of block, where the thing you wait for is not an issue. A
person, a document, a machine and a span of days are each such a thing.
A wait on another repository takes blocked by where an issue there tracks
it. The link crosses repositories, as
gh issue edit <n> --add-blocked-by <url> sets it. Do not add needs:time to
such a wait. When that issue closes, read it, because a close does not show that
the outcome of this issue is reachable.
A sub-issue is one part of the outcome of its parent. The parent closes at
the close of the last sub-issue. Do not use a sub-issue for work that the
outcome of the parent does not need. File that work as a separate issue, and
link it with blocked by where it must land first.