---
module: processes.gitea-issues
title: Gitea Issue Process
tab: Overview
order: 10
audience: bcos
---

How we track work in Gitea. **BCOS admin only.** Git hosting is **Gitea** (not GitHub/GitLab).

## Issue first — always
**Any work, however phrased ("just do X", "develop Y"), gets a Gitea issue created BEFORE code.**
No issue = don't start. Before fixing a bug, **search the repo's open `Bug`s first** — one may
already exist (avoid duplicates).

## Labels (org-level, shared across all `hiveiq-src` repos)
- **Type:** `Bug`, `Feature`, `Enhancement`, `Breaking`, `Documentation`, `Security`, `Testing`
- **Priority:** `Priority/Critical` · `High` · `Medium` · `Low`
- **Review/Triage:** `Reviewed/Confirmed` · `Duplicate` · `Invalid` · `Won't Fix`
- **Status:** `Status/Blocked` · `Need More Info` · `Abandoned`
- **Workflow:** `Verify Fix` (fix deployed, verify before closing) · `Reopen` (regressed) ·
  `Tested/Not Fixed`

> **Release is tracked by milestone, not by label.** Do **not** create `Release/*` labels — an
> earlier attempt was retired in favour of milestones (see below).

## Milestones — which release an issue ships in
A Gitea **milestone** marks the target release for an issue. Milestones (not labels) are the
release-planning mechanism: one issue = one milestone (releases are mutually exclusive), and each
milestone gives a due date + automatic burndown (open/closed %).

- **Naming:** calendar `YYYY-MM` for a dated release — e.g. **`2026-07`**, due the last day of the
  month. This makes it obvious when work ships.
- **`Next`** — a milestone with **no due date**: staged for the next release, date TBD.
- **Backlog** — simply **no milestone assigned**. Don't create a "Backlog" milestone.

**Gitea milestones are per-repo** (there is no org-level milestone). So:
- Create the milestone in a repo the **first time** a matching issue is assigned there
  (on-demand — avoids empty milestones cluttering repos with no work for that release).
- For a **cross-repo rollup** of everything in a release, query the search API:
  `GET /repos/issues/search?state=open&milestones=2026-07&owner=hiveiq-src`.

**Assigning:** `PATCH /repos/hiveiq-src/<repo>/issues/<n>` with `{"milestone": <milestone_id>}`
(the numeric id from the repo's milestone, not the title). Every fix we work gets assigned to its
target release milestone.

### Release history (backfilled 2026-07-05)
The full closed-issue history is milestoned by **close date** — a continuous pre-pilot → pilot →
production record. Each historical milestone is per-repo and **closed at 100%** (they represent
completed releases, not plans):

| Milestone | Issues | Stage |
|---|---|---|
| `2026-02` + `2026-03` | 5 + 22 | pre-pilot (early development) |
| `2026-04` | 42 | April pilot |
| `2026-05` | 83 | May pilot |
| `2026-06` | 105 | **production go-live — 17 June 2026** |
| `2026-07` | current | live release (in progress) |

- Backfill was scoped to issues that **actually closed** in each month (by `closed_at`), not
  "everything prior" — so each milestone reflects reality (a completed release), not a catch-all.
- **Gotcha:** the search API returns newest-first and caps at ~200 results — **paginate**
  (`&limit=50&page=N`) or older months get truncated (June's newer issues hid Feb–May on the first pass).
- Rollup any release across repos: `GET /repos/issues/search?milestones=<YYYY-MM>&owner=hiveiq-src`.

### Viewing a milestone (release-prep)
To scan what a release contains, view its issues per repo in the Gitea web UI:

- **Per-repo, clickable:** `…/hiveiq-src/<repo>/issues?type=all&state=open&milestone=<id>`. The
  `milestone=` param is the **per-repo numeric id** — Gitea's web filter has no by-name option, so
  a `2026-07` link differs per repo. Get the id: `GET /repos/hiveiq-src/<repo>/milestones?state=all`
  → match `title == "<YYYY-MM>"`.
- **Cross-repo, one query (no ids):** `GET /repos/issues/search?state=open&milestones=<YYYY-MM>&owner=hiveiq-src`
  (the rollup above) — best for "everything targeted at the release."

**Regenerate the current-month per-repo links** (works for any month — never hardcode ids, they
rotate each release and close when the milestone does):

```bash
MONTH=2026-07; TOKEN=<hiveiq token>; BASE=https://hiveiq-gitea.directlx.dev
for r in $(GIT_SSL_NO_VERIFY=true curl -s "$BASE/api/v1/orgs/hiveiq-src/repos?limit=100" \
  -H "Authorization: token $TOKEN" | python3 -c "import sys,json;[print(x['name']) for x in json.load(sys.stdin)]"); do
  GIT_SSL_NO_VERIFY=true curl -s "$BASE/api/v1/repos/hiveiq-src/$r/milestones?state=all&limit=100" \
    -H "Authorization: token $TOKEN" \
  | python3 -c "import sys,json;r='$r';b='$BASE';m='$MONTH';[print(f'{r}: {b}/hiveiq-src/{r}/issues?type=all&state=open&milestone='+str(x['id'])) for x in json.load(sys.stdin) if x['title']==m]"
done
```

## Assignment
- **New issue** → assign `hiveiq` (triage / fix owner).
- **Fix deployed** → reassign to the verifier and apply **`Verify Fix`**.
  - **Frontend** fixes → **tristan** (QA). **Backend** fixes → **johannes** (tristan can't test backend).
  - `erin` has no Gitea account yet — assign only `tristan` until she does.

## Closing — always manual (and it now notifies the reporter)
**Never close an issue via the API.** Claude applies labels and comments only. The team closes
issues in the Gitea UI after verification. Never PATCH `state: closed`.

> **Closing an issue auto-notifies the reporter.** A Gitea webhook (`gitea-notify`, built by
> dlx-claude 2026-07-01) sends the reporter a DIRECT message in their HiveIQ inbox when an issue is
> closed — using the **Resolution** field from the issue template. **Fill in the Resolution before
> closing** so the customer gets a meaningful message. (Reporter email is read from the issue body;
> if they're not a HiveIQ user, the notification is silently skipped.)

## A fix isn't done until its issue is reconciled
After deploying a fix: **comment the fix on the matching issue + apply `Verify Fix` + assign the
verifier.** Never leave a deployed fix with a stale/untouched issue.

## PR approval (ties to [[processes.main]])
Johannes approves the PR in the Gitea UI (SOC2 gate) — never Claude. After approval, Claude merges
via API.

## API note
Create/label/assign via the Gitea REST API with the `hiveiq` token. `directlx` = human reviewer
(Johannes). Use `GIT_SSL_NO_VERIFY=true` for Gitea calls (self-signed internal cert).
