Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/labels carries an ETag and answers a repeat with 304 until a label or its open count changes #9170
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/labels` now carries a weak ETag and answers a repeat with 304 until a label or its open count changes.
shipped production is serving it deploy 84c60a0
GET /api/v1/repos/:owner/:name/labels now carries a weak ETag and answers a repeat with 304 until a label or its open count changes.
test/labels-etag.test.ts passes, tsc --noEmit is clean and scripts/gate.ts printed nothing. I also ran the labels-adjacent suites that exist (api-labels-update, repo-reads-timing, parity, openapi-oa-pull-hold), and they pass. I did not run the full suite or scripts/fast.ts; the forge runs the full check on push.
- Headers: a 200 carries
etag(fromweakEtagover the rendered page),cache-control: private, no-cacheandvary: authorization, cookie. A repeat holding the tag inIf-None-Matchgets a 304 with the same three headers and no body, plus the floor headers. - Body: the body is the same bytes as before. I tag what
pageOfalready renders, so the open counts the reader sees are inside the tag. - Sealed issues: a writer and a signed-out reader get different tags once a sealed issue makes their open counts differ, so a reader's tag never matches a writer's body.
- Errors: a 404 or a bad page parameter returns the existing response unchanged, with no ETag.
- Files: the change is in
src/http/api/labels.ts(the GET handler is now async) and the new test file.src/http/api.tsis untouched. - Tests: they cover 200 then 304, the tag moving on add, rename, remove, labelling an issue, and closing and reopening it, the writer/reader split, and the 404 and bad-page cases. I did not run them against the old code to confirm they fail without the change.
I left out ETags on the label write routes (POST, PATCH, DELETE). Each page and limit combination gets its own tag, as the issues list does.
To try it:
curl -si -H "authorization: Bearer $T" $URL/api/v1/repos/o/n/labels
Then repeat with -H 'if-none-match: <etag>' to get a 304.
GET /api/v1/repos/:owner/:name/labels now carries a weak ETag and answers a repeat with a 304 until a label or its open count changes.
The check failed because no commit message had a Try: line. I added one in a new commit and pushed it. bun scripts/gate.ts now passes locally. I didn't re-run the tests, since the new commit is empty and the code is unchanged.
What changed
- A 200 from the labels route carries an
etagfromweakEtagover the rendered page, pluscache-control: private, no-cacheandvary: authorization, cookie. The body is the same bytes as before. - A repeat holding that tag in
If-None-Matchgets a 304 with the same headers, no body, and the security headers. - The tag moves when a label is added, renamed or removed. It also moves when an issue carrying the label opens or closes.
- A writer and a signed-out reader get different tags once a sealed issue makes their open counts differ. A reader never gets a 304 from a writer's tag.
- A 404 or a bad page parameter returns the existing response with no ETag.
- The change is in
src/http/api/labels.tsandtest/labels-etag.test.ts.src/http/api.tsis untouched.
Not done: the label write routes (POST, PATCH, DELETE) carry no ETag. Each page and limit combination gets its own tag, as the issues list does.
Try: GET /api/v1/repos/<owner>/<name>/labels on the preview. Then repeat it with If-None-Match set to the etag it returned, and expect a 304.
Clean. Mirrors the issues-list ETag pattern, reuses weakEtag/holds, tag is over the rendered page so per-reader open counts are inside it, 404/bad-page return early without a tag. Tests cover each behaviour. No security, dependency or docs concerns.
GET /api/v1/repos/:owner/:name/labelsnow carries a weak ETag and answers a repeat with 304 until a label or its open count changes.test/labels-etag.test.tspasses,tsc --noEmitis clean andscripts/gate.tsprinted nothing. I also ran the labels-adjacent suites that exist (api-labels-update,repo-reads-timing,parity,openapi-oa-pull-hold), and they pass. I did not run the full suite orscripts/fast.ts; the forge runs the full check on push.etag(fromweakEtagover the rendered page),cache-control: private, no-cacheandvary: authorization, cookie. A repeat holding the tag inIf-None-Matchgets a 304 with the same three headers and no body, plus the floor headers.pageOfalready renders, so the open counts the reader sees are inside the tag.src/http/api/labels.ts(the GET handler is now async) and the new test file.src/http/api.tsis untouched.I left out ETags on the label write routes (POST, PATCH, DELETE). Each page and limit combination gets its own tag, as the issues list does.
To try it:
Then repeat with
-H 'if-none-match: <etag>'to get a 304.Closes #9164