--- name: gitea-silent-ignore-patch-trap description: "Gitea API answers success codes while silently ignoring mutation bodies: labels in PATCH /issues/N, and any JSON body sent without Content-Type: application/json. Verify mutations with a follow-up GET, never the status code." --- # Gitea silent-ignore family (issue mutations) Two verified failure modes on the Dankclaw Gitea API (1.27.3, verified live 2026-10-21 on `dank/franksign`); both **return a success code and change nothing**: 1. **`PATCH /repos/{o}/{r}/issues/{n}` with `{"labels":[...]}`** — 200, labels untouched, even *with* a proper JSON Content-Type. Labels move only via the dedicated endpoints: - add: `POST /issues/{n}/labels` with `{"labels":[]}` - replace: `PUT /issues/{n}/labels` with `{"labels":[ids]}` (works without Content-Type) - remove: `DELETE /issues/{n}/labels/{id}` 2. **Any JSON body without `header = "Content-Type: application/json"` in the curl config** — e.g. closing with `PATCH {"state":"closed"}` returns **201 and no-ops**; identical request with the header closes. curl's default `-d` type (`application/x-www-form-urlencoded`) makes this silent. ## Rules - Mutating JSON calls: always send `Content-Type: application/json` alongside `Authorization: token` in the same `-K` config file. - **Never trust the status code for mutations — follow up with `GET` and read the field** (`labels`, `state`). Both traps look like success otherwise. - Token stays out of `-H` args (world-readable `/proc//cmdline`): extract from `~/.config/secrets/gitea` into a 600-perm curl config, use `-K`. ## Quick verification idiom ```bash curl -sK "$cfg" "http://192.168.0.143:3000/api/v1/repos/dank/franksign/issues/22" \ | jq '{state, labels: [.labels[].name]}' ``` Franksign's vault page `franksign-docs/agents/issue-tracker.md` owns the repo-local copy of this law; this skill exists so other repos (serverAppConfigs, command-center) don't re-burn the cycle.