Public: anyone can read this; the crew can change it.
GET /api/v1/orgs/:org carries an ETag and answers a repeat with 304 until the organisation changes #9237
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/orgs/:org` now carries a weak ETag and answers a repeat with 304. The new `test/org-etag.test.ts`, `test/org-teams-etag.test.ts` and the other tests that call `/api/v1/orgs/` pass (73 across 8 files). `bun x tsc --noEmit` and `bun scripts/gate.ts` also ran clean.
shipped production is serving it deploy 674862e
GET /api/v1/orgs/:org now carries a weak ETag and answers a repeat with 304. The new test/org-etag.test.ts, test/org-teams-etag.test.ts and the other tests that call /api/v1/orgs/ pass (73 across 8 files). bun x tsc --noEmit and bun scripts/gate.ts also ran clean.
What changed
- The route in
src/http/api/orgs.tsnow builds its body through thetagged()helper that the teams routes already use. That helper tags the exact JSON text withweakEtagand setscache-control: private, no-cacheandvary: authorization, cookie. - A repeat holding the tag in
If-None-Matchgets a 304 with the same headers and no body. - The body is the same JSON as before, field for field.
src/http/api.tsis untouched.
Tests (test/org-etag.test.ts):
- The 200 → 304 round trip, including the same headers and the security-floor headers on the 304.
- The tag moves when the organisation's display name changes and when a team is added.
- The tag moves when a member joins a team the viewer is in, and two viewers shown different bodies get different tags.
- An unknown organisation gets a 404 with no ETag, even with
If-None-Match: *, signed in or not.
The body is only id, username, displayName, kind, createdAt and the viewer's visible teams. Three of the issue's cases don't apply to it:
- Rename: an org can't be renamed through the API (
updateProfilerefuses orgs), so the test changes the stored display name directly. - Description and visibility: orgs have neither, so there is nothing to test.
- Members: a member joining or leaving moves the tag only for a viewer whose visible teams change.
The access check was already only "does the org exist", since the route is readable by anyone. I kept that, so a 404 always comes before any 304.
scripts/fast.ts reported the diff too broad to name (1120 files), so I ran the org tests by hand instead of the full suite.
GET /api/v1/orgs/:org now sends a weak ETag and answers a repeat with 304 while the organisation is unchanged. I haven't seen the forge's check run on this push. The previous run failed only because no commit message had a "Try:" line, so I added an empty commit carrying one. The code and tests are unchanged since the earlier run, where the org ETag tests and the other tests that call /api/v1/orgs/ passed.
What changed
src/http/api/orgs.ts: the route now builds its body through thetagged()helper the org teams routes already use. It tags the exact JSON text withweakEtagand setscache-control: private, no-cacheandvary: authorization, cookie. A repeat holding the tag inIf-None-Matchgets a 304 with the same headers, no body and the security-floor headers.- The body is the same JSON as before.
src/http/api.tsis untouched. test/org-etag.test.tscovers the 200 then 304 round trip, the tag moving on a display-name change and a new team, the tag moving when a member joins a team, two viewers never sharing a tag, and an unknown organisation staying 404 with no tag even forIf-None-Match: *.
What I left out
- Rename, description and visibility: the body is only
id,username,displayName,kind,createdAtand the viewer's visibleteams, and orgs have no description or visibility. The API can't rename an org (updateProfilerefuses them), so the test changes the stored display name directly. - A member joining or leaving moves the tag only for a viewer whose visible teams change.
- The route has no access check beyond the org existing, so the 404 for an unknown org still comes before any 304.
- I ran the org tests by hand.
scripts/fast.tscalled the diff too broad to name.
How to try it: On the preview, curl -si -H 'authorization: Bearer <token>' <preview>/api/v1/orgs/<org>. Then repeat it with If-None-Match set to the returned etag and expect a 304.
🤖 Generated with Claude Code
Clean one-line change reusing the existing tagged() helper; 404 precedes any 304; tests cover the round trip, tag movement, per-viewer tags and the 404 case. No security, doc or dependency issues. Preview 404 path verified.
GET /api/v1/orgs/:orgnow carries a weak ETag and answers a repeat with 304. The newtest/org-etag.test.ts,test/org-teams-etag.test.tsand the other tests that call/api/v1/orgs/pass (73 across 8 files).bun x tsc --noEmitandbun scripts/gate.tsalso ran clean.What changed
src/http/api/orgs.tsnow builds its body through thetagged()helper that the teams routes already use. That helper tags the exact JSON text withweakEtagand setscache-control: private, no-cacheandvary: authorization, cookie.If-None-Matchgets a 304 with the same headers and no body.src/http/api.tsis untouched.Tests (
test/org-etag.test.ts):If-None-Match: *, signed in or not.The body is only
id,username,displayName,kind,createdAtand the viewer's visibleteams. Three of the issue's cases don't apply to it:updateProfilerefuses orgs), so the test changes the stored display name directly.The access check was already only "does the org exist", since the route is readable by anyone. I kept that, so a 404 always comes before any 304.
scripts/fast.tsreported the diff too broad to name (1120 files), so I ran the org tests by hand instead of the full suite.Closes #9226