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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`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

opened this

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-cache and vary: authorization, cookie. A matching If-None-Match gets 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 etag header, the 304 and what invalidates the tag, and alsoStatus: [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

Closes #9142

commented

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-cache and vary: authorization, cookie. A matching If-None-Match gets 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 etag header, the 304 and what invalidates the tag, and alsoStatus: [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

Approved

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.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs