Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/pulls/:number carries an ETag and answers a repeat with 304 until the pull's head, checks, review or state change #9146
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/pulls/:number` now carries a weak `etag` and answers a repeat with 304 until the pull changes.
shipped production is serving it deploy 1018b7a
GET /api/v1/repos/:owner/:name/pulls/:number now carries a weak etag and answers a repeat with 304 until the pull changes.
What changed
- Route (
src/http/api.ts): The tag comes from the exact JSON string sent, so a new head, check verdict, review or merge changes it. No cached time is involved. - Headers: The 200 carries
etag,cache-control: private, no-cacheandvary: authorization, cookie. A matchingIf-None-Matchgets a 304 with no body and the same headers. - Access order: The access check runs before the tags are compared, so a stranger asking for a private pull gets a 404 whatever tag they hold.
- OpenAPI: The route summary now names the
etagheader, the 304 and what invalidates the tag, andalsoStatus: [304]is set. - Test (
test/pull-etag.test.ts): It covers 200 then 304, then a new check verdict giving a 200 with a new tag, and the stranger's 404.
What I left out: nothing beyond the issue. I did not touch the other routes, which already have their own tags.
Checks: The new test passes, tsc --noEmit is clean, and scripts/gate.ts passes. I did not run the full suite, since the forge runs it on the push.
How to try it: Call /api/v1/repos/trevato/forge/pulls/<n> on the preview and note the etag. Repeat the call with If-None-Match: <that etag> and you get a 304 with no body.
The gate wanted a Try: line in a commit message, so I added an empty commit carrying it.
🤖 Generated with Claude Code
Clean. The tag is computed from the exact JSON string sent, after loadPull has checked access (a stranger gets 404 with no etag). It uses the existing weakEtag/holds helpers. Headers are as specified, the 304 carries the same headers, the OpenAPI route names the etag and the 304, and the test covers 200, 304, a new verdict giving 200 with a new tag, and the stranger 404. No new dependency, and nothing was added to the README.
GET /api/v1/repos/:owner/:name/pulls/:numbernow carries a weaketagand answers a repeat with 304 until the pull changes.What changed
src/http/api.ts): The tag comes from the exact JSON string sent, so a new head, check verdict, review or merge changes it. No cached time is involved.etag,cache-control: private, no-cacheandvary: authorization, cookie. A matchingIf-None-Matchgets a 304 with no body and the same headers.etagheader, the 304 and what invalidates the tag, andalsoStatus: [304]is set.test/pull-etag.test.ts): It covers 200 then 304, then a new check verdict giving a 200 with a new tag, and the stranger's 404.What I left out: nothing beyond the issue. I did not touch the other routes, which already have their own tags.
Checks: The new test passes,
tsc --noEmitis clean, andscripts/gate.tspasses. I did not run the full suite, since the forge runs it on the push.How to try it: Call
/api/v1/repos/trevato/forge/pulls/<n>on the preview and note theetag. Repeat the call withIf-None-Match: <that etag>and you get a 304 with no body.The gate wanted a
Try:line in a commit message, so I added an empty commit carrying it.🤖 Generated with Claude Code
Closes #9142