Public: anyone can read this; the crew can change it.
GET /api/v1/repos/:owner/:name/egress-grants carries an ETag and answers a repeat with 304 until a grant is added, used or revoked #9184
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/egress-grants` now sends a weak ETag, so an admin's agent that asks again with the same tag gets a 304 with no body until a grant changes.
shipped production is serving it deploy e209d31
GET /api/v1/repos/:owner/:name/egress-grants now sends a weak ETag, so an admin's agent that asks again with the same tag gets a 304 with no body until a grant changes.
What changed
- In
src/http/api/egress_grants.ts, the read builds the same JSON body as before and tags it withweakEtag. The 200 also sendscache-control: private, no-cacheandvary: authorization, cookie. - A request whose
If-None-Matchmatches the tag gets a 304 with the same headers and no body. ThesecurityFloorheaders are still on it because the server adds them. - The tag is added only after the scope, repository and admin checks pass. So a token short of admin, a non-admin and a missing repository answer as they do today, with no ETag, even with
If-None-Match: *. - This uses the same
taggedhelper as the hooks route (#9173).src/http/api.ts, the POST and DELETE routes and the egress proxy are untouched.
What to look at
- Refused requests don't move the tag. The issue asks that a refused request the view lists moves it. This view lists only the destinations and the grant and revoke history, not refused attempts. A refused grant writes nothing, so the tag holds, and the test checks that it does. If refused proxy requests should count, the view has to show them first, which belongs in another issue.
- Changes to forge.json move the tag but aren't tested. Adding or removing a destination there changes the body, so the tag moves too.
Tests
test/egress-grants-etag.test.tscovers the 200 headers, the 304 and its security headers, granting and revoking moving the tag, a refused grant leaving it, and the three refusals with*. With the route change removed, 4 of its 6 tests fail.- That file,
test/api-egress-grants-timing.test.tsandtest/app-egress-grants.test.tspass, as dobun scripts/gate.tsandbun x tsc --noEmit.
To try it: call GET /api/v1/repos/<owner>/<name>/egress-grants with an admin token, then again with If-None-Match: <etag> to get the 304. A POST of a destination that forge.json asks for gives you a 200 with a new tag.
🤖 Generated with Claude Code
GET /api/v1/repos/:owner/:name/egress-grants now sends a weak ETag, so an admin's agent that asks again with the same tag gets a 304 with no body until a grant changes.
What changed
- In
src/http/api/egress_grants.ts, the read builds the same JSON body as before and tags it withweakEtag. The 200 also sendscache-control: private, no-cacheandvary: authorization, cookie. - A request whose
If-None-Matchmatches the tag gets a 304 with the same headers and no body. ThesecurityFloorheaders are still on it because the server adds them. - The tag is added only after the scope, repository and admin checks pass. So a token short of admin, a non-admin and a missing repository answer as they do today, with no ETag, even with
If-None-Match: *. - This uses the same
taggedhelper as the hooks route (#9173).src/http/api.ts, the POST and DELETE routes and the egress proxy are untouched. - The last commit adds no code, only the
Try:line the gate requires. It is a separate commit because history can't be force-pushed.
What to look at
- Refused requests don't move the tag. The issue asks that a refused request the view lists moves it. This view lists only the destinations and the grant and revoke history, not refused attempts. A refused grant writes nothing, so the tag holds, and the test checks that it does. If refused proxy requests should count, the view has to show them first, which belongs in another issue.
- Changes to forge.json move the tag but aren't tested. Adding or removing a destination there changes the body, so the tag moves too.
Tests
test/egress-grants-etag.test.tscovers the 200 headers, the 304 and its security headers, granting and revoking moving the tag, a refused grant leaving it, and the three refusals with*. With the route change removed, 4 of its 6 tests fail.bun scripts/gate.tspasses with the newTry:line, and the new test file passes (6 of 6).- Before this push:
test/api-egress-grants-timing.test.ts,test/app-egress-grants.test.tsandbun x tsc --noEmitpassed. The only commit since then adds no code, so I didn't rerun them.
To try it on the preview: call GET /api/v1/repos/<owner>/<name>/egress-grants with an admin token, then again with If-None-Match: <etag> to get the 304.
🤖 Generated with Claude Code
Clean: tag is added only after scope/repo/admin checks, weak etag over the rendered body, private no-cache and vary set, 304 is bodiless and the server adds the floor. Tests cover each case. Refused grants write nothing the view lists, so the tag holding is correct. Notes: the local tagged helper repeats a pattern already in several modules (non-blocking).
GET /api/v1/repos/:owner/:name/egress-grantsnow sends a weak ETag, so an admin's agent that asks again with the same tag gets a 304 with no body until a grant changes.What changed
src/http/api/egress_grants.ts, the read builds the same JSON body as before and tags it withweakEtag. The 200 also sendscache-control: private, no-cacheandvary: authorization, cookie.If-None-Matchmatches the tag gets a 304 with the same headers and no body. ThesecurityFloorheaders are still on it because the server adds them.If-None-Match: *.taggedhelper as the hooks route (#9173).src/http/api.ts, the POST and DELETE routes and the egress proxy are untouched.What to look at
Tests
test/egress-grants-etag.test.tscovers the 200 headers, the 304 and its security headers, granting and revoking moving the tag, a refused grant leaving it, and the three refusals with*. With the route change removed, 4 of its 6 tests fail.test/api-egress-grants-timing.test.tsandtest/app-egress-grants.test.tspass, as dobun scripts/gate.tsandbun x tsc --noEmit.To try it: call
GET /api/v1/repos/<owner>/<name>/egress-grantswith an admin token, then again withIf-None-Match: <etag>to get the 304. APOSTof a destination that forge.json asks for gives you a 200 with a new tag.🤖 Generated with Claude Code
Closes #9179