Skip to content

Add Vary: Accept header and document nginx content negotiation config#3548

Open
mich-elle-luna wants to merge 2 commits into
mainfrom
content-negotiation
Open

Add Vary: Accept header and document nginx content negotiation config#3548
mich-elle-luna wants to merge 2 commits into
mainfrom
content-negotiation

Conversation

@mich-elle-luna

@mich-elle-luna mich-elle-luna commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator
  • Add Vary: Accept to all hugo serve responses so caches know responses vary by Accept header
  • Serve .html.md files with Content-Type: text/markdown during local dev
  • Document the production nginx config needed for HTTP-level content negotiation (Accept: text/markdown → serve index.html.md) in AI_AGENT_DEVELOPER_GUIDE.md

The element is already generated for every page by the AlternativeOutputFormats loop in baseof.html — no changes needed there.


Note

Low Risk
Changes are limited to Hugo config, dev-server headers, and operator documentation; no application logic or auth paths are modified.

Overview
Improves agent-ready HTTP behavior for markdown alternates: local hugo serve now sends Vary: Accept on all responses and Content-Type: text/markdown for *.html.md, and the homepage is included in Hugo [outputs] so it builds Markdown (and JSON) like sections and pages.

Adds a Production Configuration for Agent Readiness section to AI_AGENT_DEVELOPER_GUIDE.md with a full nginx recipe (Accept: text/markdown → rewrite to index.html.md, MIME mapping, Vary), plus a note that hugo serve does not rewrite by Accept—only production (or CDN) rules enable true negotiation. No template changes; alternate <link> tags remain from existing AlternativeOutputFormats in baseof.html.

Reviewed by Cursor Bugbot for commit 951b34e. Bugbot is set up for automated code reviews on this repo. Configure here.

- Add Vary: Accept to all hugo serve responses so caches know responses
  vary by Accept header
- Serve .html.md files with Content-Type: text/markdown during local dev
- Document the production nginx config needed for HTTP-level content
  negotiation (Accept: text/markdown → serve index.html.md) in
  AI_AGENT_DEVELOPER_GUIDE.md

The <link rel="alternate" type="text/markdown"> element is already
generated for every page by the AlternativeOutputFormats loop in
baseof.html — no changes needed there.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jit-ci

jit-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

@mich-elle-luna mich-elle-luna requested a review from a team June 24, 2026 21:47
@andy-stark-redis

andy-stark-redis commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

I don't know much about the server config but below is what the LLMs had to say about it. This might be a job for the new docs:assess-comments slash command, btw :-)

Content negotiation review — verdict

Two independent reviews (Claude + Codex) of the content-negotiation setup in config.toml and the nginx snippet in AI_AGENT_DEVELOPER_GUIDE.md. No files were changed. The Hugo output wiring and the dynamically generated <link rel="alternate" type="text/markdown"> (in layouts/_default/baseof.html) are sound; the issues are concentrated in output-kind coverage and the nginx example.

Agreed findings (both reviewers)

  • add_header Content-Type is the wrong directive (AI_AGENT_DEVELOPER_GUIDE.md ~L234). nginx sets MIME from types/default_type; .md isn't in the stock table, so the file is served as application/octet-stream and add_header may duplicate rather than replace it. Use default_type text/markdown; or a types { text/markdown md; } block.
  • Accept matching is a loose substring (~L227). It ignores q-values, so text/markdown;q=0 (explicit refusal) still gets served markdown, and it would match stray tokens. Not true negotiation.
  • Rewrite regex skips any path containing a dot (~L244). ^([^.]+)$ misses /…/index.html and version directories (e.g. 7.4), which fall through to HTML.

Caught by Codex (highest impact)

  • [outputs] omits the home kind (config.toml L125-127). Only section and page are listed, so the homepage (and any taxonomy/term pages) fall back to Hugo's default [HTML, RSS] and emit no .html.md or .json. This directly contradicts the guide's "every page generates a markdown version." Verified.
  • add_header … always flag missing (~L235, L239). Without always, nginx drops the Vary: Accept header on non-2xx/3xx responses (e.g. 404/5xx), weakening cache correctness.

Caught by Claude

  • Dev ≠ prod. The config.toml [server] block only sets headers — it does no Accept-based rewriting. hugo serve with Accept: text/markdown returns HTML, not markdown; the guide's wording invites the wrong assumption.
  • Verified the rel="alternate" claim genuinely holds (generated in baseof.html L7-9, not hardcoded).

Suggested priority

  1. Add home (+ taxonomy/term if wanted) to [outputs] — otherwise the homepage has no markdown/JSON variant.
  2. Fix the nginx Content-Type (use default_type/types, not add_header).
  3. Add always to the Vary headers.
  4. Harden the rewrite (dot-paths) and Accept match (q-values).
  5. Note in the guide that the dev server does headers only, not negotiation.

Reviewed by Claude (Opus 4.8) and Codex; no code changed.

- Add home to [outputs] so the homepage and taxonomy pages emit
  .html.md and .json variants (previously fell back to [HTML, RSS])
- Fix nginx Content-Type: use types{} block instead of add_header so
  .md files get the correct MIME from the types table rather than a
  header that can't override nginx's own content-type
- Add always flag to all Vary: Accept add_header directives so the
  header is sent on non-2xx responses (404/5xx) for cache correctness
- Harden Accept map: exclude q=0 (explicit refusal) to avoid serving
  markdown to clients that don't want it
- Fix rewrite regex: replace [^.]+ (skips dot-paths) with [^.]*[^/]
  so version directories (7.4/) and dotless paths are handled correctly
- Clarify dev server limitation: hugo serve sets headers only and does
  not perform Accept-based rewriting; full negotiation requires nginx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 951b34e. Configure here.

# are intentionally left to try_files so they resolve normally.
if ($use_markdown) {
rewrite ^(.*/)$ $1index.html.md last;
rewrite ^(/[^.]*[^/])$ $1/index.html.md last;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing-slash rewrite ignores dot exclusion

Medium Severity

The guide states paths with a dot (e.g. version segments like 7.4/) skip negotiation and use try_files, but the first rewrite ^(.*/)$ runs for every trailing-slash URL when Accept selects markdown. Those dotted paths still rewrite to index.html.md, which can 404 or return markdown where normal HTML index resolution was intended.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 951b34e. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants