Public: anyone can read this; the crew can change it.
/llms.txt revalidates on every read, so its "Running … at <commit>" line is never an hour stale after a deploy #8470
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/8464-llms-txt-revalidates-on-every-read-so-it → main 2 commits fork
3 files
changed +28
−2
src/web/standards.ts +3 −1
| @@ -370,11 +370,13 @@ export function llmsTxt(ctx: Ctx): Response { | ||
| 370 | 370 | if (llmsBodies.size >= 16) llmsBodies.clear(); |
| 371 | 371 | llmsBodies.set(key, kept); |
| 372 | 372 | } |
| 373 | const cache = "public, max-age=3600"; | |
| 373 | // The third line names the running commit: a client revalidates (one small 304) rather than keep an old build for an hour. | |
| 374 | const cache = "private, no-cache"; | |
| 374 | 375 | const sent = ctx.request.headers.get("if-none-match"); |
| 375 | 376 | if (sent !== null && sent.split(",").some((t) => t.trim() === kept.etag)) |
| 376 | 377 | return new Response(null, { status: 304, headers: { etag: kept.etag, "cache-control": cache, vary: "Cookie, Authorization" } }); |
| 377 | 378 | const res = text(kept.body, "text/markdown; charset=utf-8"); |
| 379 | res.headers.set("cache-control", cache); | |
| 378 | 380 | res.headers.set("etag", kept.etag); |
| 379 | 381 | res.headers.append("vary", "Cookie, Authorization"); |
| 380 | 382 | return res; |
test/llms-tools.test.ts +1 −1
| @@ -39,7 +39,7 @@ describe("/llms.txt MCP tools", () => { | ||
| 39 | 39 | test("lists, for a signed-in reader, exactly the tools in the index and `report`, by their `doc`, with (admin) and (writes)", async () => { |
| 40 | 40 | const res = await fetch(`${servers.url}/llms.txt`, { headers: { authorization: `Bearer ${token}` } }); |
| 41 | 41 | expect(res.status).toBe(200); |
| 42 | expect(res.headers.get("cache-control")).toContain("max-age"); | |
| 42 | expect(res.headers.get("cache-control")).toContain("no-cache"); | |
| 43 | 43 | const lines = (await res.text()).split("\n"); |
| 44 | 44 | const start = lines.indexOf("## MCP tools"); |
| 45 | 45 | expect(start).toBeGreaterThan(-1); |
test/standards.test.ts +24
| @@ -270,6 +270,10 @@ describe("what the open web expects", () => { | ||
| 270 | 270 | expect(again.status).toBe(304); |
| 271 | 271 | expect(await again.text()).toBe(""); |
| 272 | 272 | expect(again.headers.get("etag")).toBe(etag); |
| 273 | expect(first.headers.get("cache-control")).toBe("private, no-cache"); | |
| 274 | expect(again.headers.get("cache-control")).toBe("private, no-cache"); | |
| 275 | expect(again.headers.get("vary")).toContain("Cookie"); | |
| 276 | expect(again.headers.get("x-content-type-options")).toBe("nosniff"); | |
| 273 | 277 | const made = await createRepo(app, alice, { name: "etagged", visibility: "public" }); |
| 274 | 278 | if (!made.ok) throw new Error(String(made.error)); |
| 275 | 279 | // A new repository reaches the listing when its minute is up. |
| @@ -280,6 +284,26 @@ describe("what the open web expects", () => { | ||
| 280 | 284 | expect(await after.text()).toContain("alice/etagged"); |
| 281 | 285 | }); |
| 282 | 286 | |
| 287 | test("llms.txt changes its ETag when the running commit changes", async () => { | |
| 288 | const saved = { c: process.env["FORGE_COMMIT"], a: process.env["FORGE_APP_COMMIT"] }; | |
| 289 | try { | |
| 290 | delete process.env["FORGE_COMMIT"]; | |
| 291 | process.env["FORGE_APP_COMMIT"] = "aaaaaaa1111111"; | |
| 292 | const one = await fetch(`${base}/llms.txt`); | |
| 293 | await one.text(); | |
| 294 | process.env["FORGE_APP_COMMIT"] = "bbbbbbb2222222"; | |
| 295 | const two = await fetch(`${base}/llms.txt`, { headers: { "if-none-match": one.headers.get("etag")! } }); | |
| 296 | expect(two.status).toBe(200); | |
| 297 | expect(two.headers.get("etag")).not.toBe(one.headers.get("etag")); | |
| 298 | expect(await two.text()).toContain("at bbbbbbb"); | |
| 299 | } finally { | |
| 300 | for (const [k, v] of [["FORGE_COMMIT", saved.c], ["FORGE_APP_COMMIT", saved.a]] as const) { | |
| 301 | if (v === undefined) delete process.env[k]; | |
| 302 | else process.env[k] = v; | |
| 303 | } | |
| 304 | } | |
| 305 | }); | |
| 306 | ||
| 283 | 307 | test("llms.txt opens with the version, and the short commit only when one is known", async () => { |
| 284 | 308 | const saved = { |
| 285 | 309 | v: process.env["FORGE_VERSION"], |
new above
offline