Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/builds carries an ETag and answers a repeat with 304 when no pin has changed #9132
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/builds` now carries a weak `etag` and answers a repeat `If-None-Match` with an empty 304. The new test, the MCP size and builds tests, the OpenAPI and contract tests, `bun x tsc --noEmit` and `scripts/gate.ts` all pass. I did not run the full suite; the forge's check does that on this push.
shipped production is serving it deploy 652cd9c
GET /api/v1/repos/:owner/:name/builds now carries a weak etag and answers a repeat If-None-Match with an empty 304. The new test, the MCP size and builds tests, the OpenAPI and contract tests, bun x tsc --noEmit and scripts/gate.ts all pass. I did not run the full suite; the forge's check does that on this push.
What changed
- Route (
src/http/api/builds.ts): the tag is computed from the exact JSON sent, so a new, noted or deleted pin changes it. Nothing is cached by time. The 200 and the 304 both carryetag,cache-control: private, no-cacheandvary: authorization, cookie. It usesweakEtagandholdsfromsrc/http/etag.ts, the same pattern as the issues list. - OpenAPI (
getApiV1ReposOwnerNameBuilds.ts): the summary names theetagheader and the 304, and the route lists 304 as an extra status. - Test (
test/builds-etag.test.ts):
- Two requests give 200, then 304 with the same tag and no body.
- After POST …/builds the old tag gets a 200 with a new tag.
- After a note (PATCH) the tag changes again.
- A stranger asking for a private repository's builds gets a 404 with no etag, even holding If-None-Match: *.
What I chose not to do
- No new MCP tool, and no change to the MCP size ceilings.
- Writes (POST, PATCH, DELETE) are unchanged.
How to try it
- Run
bun test test/builds-etag.test.ts. - Or
curl -ithe builds list, then repeat it with-H 'if-none-match: <etag>'.
🤖 Generated with Claude Code
GET /api/v1/repos/:owner/:name/builds now carries a weak etag, and a repeat request holding that tag gets an empty 304. The previous push failed the gate because no commit message had a Try: line. I added an empty commit with one and pushed it. bun scripts/gate.ts printed no errors locally, and the push went through without rewriting history. The forge's check on this push hasn't reported yet, so the gate isn't confirmed green on the forge.
What changed
- The 200 carries
etag,cache-control: private, no-cacheandvary: authorization, cookie. The tag is computed from the exact JSON sent, so a new, noted or deleted pin changes it. - A matching
If-None-Matchgets a 304 with no body and the same tag. - The route's OpenAPI summary names the
etagheader and the 304. test/builds-etag.test.tscovers three cases:
- Two requests give a 200, then a 304.
- A pin and a note each give a 200 with a new tag.
- A stranger asking for a private repository's builds gets a 404 with no tag, even with If-None-Match: *.
What I chose not to do
- No new MCP tool, and no change to the MCP size ceilings.
- Writes (POST, PATCH, DELETE) are unchanged.
How to try it
- Run
bun test test/builds-etag.test.ts. - Or
curl -ithe preview's/api/v1/repos/<owner>/<name>/builds, then repeat with-H 'if-none-match: <etag>'and expect 304.
🤖 Generated with Claude Code
Clean: mirrors the issues-list ETag pattern, tag is of the exact bytes sent, 404 for unreadable repos comes before any tag, vary/cache-control are right, test covers 200/304/pin/note/private. No README or new dependency.
GET /api/v1/repos/:owner/:name/buildsnow carries a weaketagand answers a repeatIf-None-Matchwith an empty 304. The new test, the MCP size and builds tests, the OpenAPI and contract tests,bun x tsc --noEmitandscripts/gate.tsall pass. I did not run the full suite; the forge's check does that on this push.What changed
src/http/api/builds.ts): the tag is computed from the exact JSON sent, so a new, noted or deleted pin changes it. Nothing is cached by time. The 200 and the 304 both carryetag,cache-control: private, no-cacheandvary: authorization, cookie. It usesweakEtagandholdsfromsrc/http/etag.ts, the same pattern as the issues list.getApiV1ReposOwnerNameBuilds.ts): the summary names theetagheader and the 304, and the route lists 304 as an extra status.test/builds-etag.test.ts):- Two requests give 200, then 304 with the same tag and no body. - After
POST …/buildsthe old tag gets a 200 with a new tag. - After a note (PATCH) the tag changes again. - A stranger asking for a private repository's builds gets a 404 with noetag, even holdingIf-None-Match: *.What I chose not to do
How to try it
bun test test/builds-etag.test.ts.curl -ithe builds list, then repeat it with-H 'if-none-match: <etag>'.🤖 Generated with Claude Code
Closes #9127