Skip to content

Use Accept header for skills HTML/markdown negotiation#1454

Merged
mantrakp04 merged 2 commits into
devfrom
fix-skills-accept-header
May 20, 2026
Merged

Use Accept header for skills HTML/markdown negotiation#1454
mantrakp04 merged 2 commits into
devfrom
fix-skills-accept-header

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented May 20, 2026

Summary

Follow-up to #1452. Sec-Fetch-Mode / Sec-Fetch-Dest didn't reliably split HTML vs. markdown at the CDN edge, so curl could still get the HTML landing page. Switch to the Accept header:

  • Browsers send Accept: text/html,... on top-level navigations.
  • curl, fetch(), and agent fetchers send */* or omit Accept.
  • Serve HTML only when text/html is explicitly listed; everything else gets SKILL.md.
  • Vary updated to Accept to match.

Test plan

  • Deploy preview
  • curl -sSL https://skill.stack-auth.com/ | head -3 returns markdown frontmatter
  • Browser load of https://skill.stack-auth.com/ still shows the HTML landing page
  • Purge Vercel cache if stale variants persist

Summary by CodeRabbit

  • Bug Fixes

    • Improved content negotiation so clients receive HTML when they clearly prefer it; otherwise resources are served as markdown to match client expectations.
  • Chores

    • Adjusted edge/CDN caching behavior to vary responses by the client's Accept header only, improving cache efficiency and more consistent content delivery.

Review Change Stack

Sec-Fetch-Mode/Sec-Fetch-Dest weren't reliable for distinguishing
browsers from curl/agents on the CDN edge. Switch to the Accept header:
browsers send `text/html,...` while curl/fetch/agents send `*/*` or omit
it, so we serve HTML only when text/html is explicitly listed. Vary
header updated to match.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-auth-mcp Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-auth-skills Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-backend Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-dashboard Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-demo Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-docs Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-preview-backend Ready Ready Preview, Comment May 20, 2026 8:22pm
stack-preview-dashboard Ready Ready Preview, Comment May 20, 2026 8:22pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8abdd26-24f3-4ef9-8e47-3a5a53cf2fa6

📥 Commits

Reviewing files that changed from the base of the PR and between cbbe071 and 5f6509e.

📒 Files selected for processing (1)
  • apps/skills/src/app/route.ts

📝 Walkthrough

Walkthrough

The skills route now uses the request Accept header for content negotiation and caching: COMMON_HEADERS Vary is set to Accept, and the route serves HTML only when text/html appears before markdown-preferring media types in Accept.

Changes

Accept Header Content Negotiation

Layer / File(s) Summary
Accept header-based content negotiation
apps/skills/src/app/route.ts
COMMON_HEADERS Vary updated to Accept. The wantsHtml logic is replaced with parsing of the Accept header; HTML is returned only when text/html is present and appears before markdown-preferring types (*/*, text/plain, text/markdown, text/x-markdown).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • hexclave/stack-auth#1454: Both PRs update apps/skills/src/app/route.ts to negotiate skills response content (HTML vs markdown) and to set the shared edge/CDN Vary header based solely on the Accept header.
  • hexclave/stack-auth#1452: Also adjusts apps/skills/src/app/route.ts HTTP caching via Vary to ensure correct caching for HTML vs markdown responses.

Poem

🐰
I sniff the headers, soft and neat,
Accept decides if you get HTML sweet.
No Sec-Fetch whispers change my tune,
I hop by method, not by dune.
Markdown waits — the header rules the room.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: switching to Accept header-based content negotiation for HTML/markdown serving on the skills endpoint.
Description check ✅ Passed The description provides comprehensive context including the motivation (follow-up to #1452), technical details of the change, and a clear test plan with specific verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-skills-accept-header

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Greptile Summary

Replaces the Sec-Fetch-Mode/Sec-Fetch-Dest browser-only headers with the standard Accept header for HTML vs. markdown content negotiation, and updates the Vary response header accordingly.

  • wantsHtml now parses the comma-separated Accept value and returns true only when text/html appears explicitly, so curl (which sends */*) and fetch() (same default) consistently receive the markdown response.
  • Vary: Accept correctly tells CDN and shared caches that the response body differs by Accept value, replacing the previous Sec-Fetch-Mode, Sec-Fetch-Dest directive that was unreliable at the edge.

Confidence Score: 4/5

Safe to merge; the change is small and the new Accept-based negotiation is more reliable than the Sec-Fetch headers it replaces.

The logic correctly handles all common client types (browsers, curl, fetch). The only gap is a missing .trim() on the media-type token after the semicolon split, which could theoretically cause a negotiation miss if a client sends whitespace before the semicolon — an RFC-permitted but practically rare format.

apps/skills/src/app/route.ts — specifically the wantsHtml parsing logic.

Important Files Changed

Filename Overview
apps/skills/src/app/route.ts Switches content negotiation from Sec-Fetch-Mode/Sec-Fetch-Dest headers to the Accept header, and updates the Vary header to match. Logic is correct for real-world browsers and curl; one minor robustness gap around whitespace trimming before the equality check on the media-type token.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant curl/agent
    participant CDN
    participant Route as route.ts GET

    Browser->>CDN: GET / Accept: text/html,...
    CDN->>Route: GET / Accept: text/html,...
    Route->>Route: wantsHtml() → true
    Route-->>CDN: 200 text/html + Vary: Accept
    CDN-->>Browser: HTML landing page (cached by Accept variant)

    curl/agent->>CDN: "GET / Accept: */* (or omitted)"
    CDN->>Route: "GET / Accept: */*"
    Route->>Route: wantsHtml() → false
    Route-->>CDN: 200 text/markdown + Vary: Accept
    CDN-->>curl/agent: SKILL.md (cached by Accept variant)
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/skills/src/app/route.ts:438
The media-type token extracted from each `Accept` segment is not trimmed before the equality check. RFC 7231 allows optional whitespace around the semicolon, so a value like `text/html ;q=0.9` (space before `;`) would produce `"text/html "` from `split(";")[0]`, which fails the strict `=== "text/html"` test and falls back to markdown instead of HTML. Adding a second `.trim()` after the split makes the match robust.

```suggestion
  return accept.split(",").some((part) => part.trim().split(";")[0].trim() === "text/html");
```

Reviews (1): Last reviewed commit: "Use Accept header for skills HTML/markdo..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the skill.stack-auth.com root route’s content negotiation so the CDN can reliably cache and serve the HTML landing page to browsers while returning the canonical SKILL.md markdown to curl/agents, using the Accept header rather than Sec-Fetch-*.

Changes:

  • Switches content negotiation logic from Sec-Fetch-Mode / Sec-Fetch-Dest to detecting an explicit text/html in Accept.
  • Updates CDN caching behavior by changing the response Vary header to Accept.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/skills/src/app/route.ts
Comment thread apps/skills/src/app/route.ts Outdated
Comment thread apps/skills/src/app/route.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/skills/src/app/route.ts`:
- Around line 434-439: The wantsHtml function fails to account for optional
whitespace and case-insensitive media types in Accept header tokens; update the
logic in wantsHtml to trim the media type after splitting on ";" and compare
using a case-insensitive match (e.g., call .trim() and .toLowerCase() on the
media type before comparing to "text/html") so entries like "text/html ; q=0.9"
and different-cased media types are correctly detected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4b01249-fc81-4f49-8446-21d36eb6ce9d

📥 Commits

Reviewing files that changed from the base of the PR and between f170e3f and cbbe071.

📒 Files selected for processing (1)
  • apps/skills/src/app/route.ts

Comment thread apps/skills/src/app/route.ts
Comment thread apps/skills/src/app/route.ts Outdated
- Trim + lowercase each media-type token so `text/html ;q=0.9` and
  mixed casing still match.
- Require text/html to appear before */*, text/plain, text/markdown,
  or text/x-markdown so a client that prefers markdown still gets it.
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.

3 participants