Skip to content

Always include Vary: Accept-Encoding for compressible responses#65579

Open
kubaflo wants to merge 2 commits intodotnet:mainfrom
kubaflo:fix/response-compression-vary-header-48008
Open

Always include Vary: Accept-Encoding for compressible responses#65579
kubaflo wants to merge 2 commits intodotnet:mainfrom
kubaflo:fix/response-compression-vary-header-48008

Conversation

@kubaflo
Copy link

@kubaflo kubaflo commented Mar 1, 2026

Fixes #48008 - The response compression middleware previously skipped entirely when the request lacked Accept-Encoding, so Vary: Accept-Encoding was never added to compressible responses. This breaks HTTP caching — caches wouldn't know to vary responses by Accept-Encoding. Now the middleware always wraps the response and adds Vary: Accept-Encoding for compressible MIME types, but only attempts actual compression when Accept-Encoding is present.

kubaflo and others added 2 commits March 1, 2026 01:17
Introduce a set of GitHub "skills" and test helpers for an end-to-end fix workflow. Adds ai-summary-comment skill (SKILL.md) and a post-ai-summary-comment.sh script that builds/upserts a single unified AI Summary comment with nested <details> sections, dry-run support, and automatic state loading from CustomAgentLogsTmp/PRState. Adds a comprehensive fix-issue skill (SKILL.md) documenting a 4-phase workflow (pre-flight, test, gate, mandatory multi-model fix) plus try-fix, verify-tests, and write-tests skill definitions. Includes test scripts under fix-issue/tests to validate comment structure, skill definitions, and dry-run previews. These changes enable standardized outputs for automated agents and a consistent PR comment/reporting mechanism for multi-model exploration and fixes.
The response compression middleware previously skipped wrapping the
response entirely when the request lacked an Accept-Encoding header.
This meant the Vary: Accept-Encoding header was never added, which
breaks HTTP caching semantics — intermediate caches wouldn't know to
vary by Accept-Encoding for responses that could be compressed.

Now the middleware always wraps the response body. When the MIME type
is compressible, Vary: Accept-Encoding is added regardless of
whether Accept-Encoding was present. Actual compression is only
attempted when Accept-Encoding is present (via the allowCompression
flag).

Fixes dotnet#48008

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 1, 2026 02:11
@kubaflo kubaflo requested review from a team, BrennanConroy and wtgodbe as code owners March 1, 2026 02:11
@github-actions github-actions bot added the area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares label Mar 1, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 1, 2026
@dotnet-policy-service
Copy link
Contributor

Thanks for your PR, @@kubaflo. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

Copy link
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

Updates ASP.NET Core’s response compression middleware so responses with compressible content types always include Vary: Accept-Encoding (even when the request lacks Accept-Encoding), improving HTTP cache correctness as described in #48008.

Changes:

  • Always wrap responses in ResponseCompressionBody, while gating actual compression on whether the request accepts compression.
  • Ensure Vary: Accept-Encoding is added for compressible responses regardless of request Accept-Encoding, and update middleware tests accordingly.
  • Add multiple new .github/skills/* skill definitions, scripts, and bash test harnesses.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Middleware/ResponseCompression/test/ResponseCompressionMiddlewareTest.cs Updates expectations so Vary: Accept-Encoding is present even when Accept-Encoding is missing.
src/Middleware/ResponseCompression/test/ResponseCompressionBodyTest.cs Updates unit tests to use the new ResponseCompressionBody constructor signature.
src/Middleware/ResponseCompression/src/ResponseCompressionMiddleware.cs Always installs ResponseCompressionBody, passing an allowCompression flag based on request headers.
src/Middleware/ResponseCompression/src/ResponseCompressionBody.cs Always appends Vary: Accept-Encoding for compressible responses; attempts compression only when allowed.
.github/skills/write-tests/SKILL.md Adds documentation for a “write-tests” workflow skill.
.github/skills/verify-tests/SKILL.md Adds documentation for verifying tests against buggy/fixed code.
.github/skills/try-fix/SKILL.md Adds documentation for trying an alternative fix approach.
.github/skills/fix-issue/tests/test-skill-definition.sh Adds bash tests intended to validate the fix-issue skill definition.
.github/skills/fix-issue/tests/test-ai-summary-comment.sh Adds bash tests intended to validate AI summary comment scripts.
.github/skills/fix-issue/SKILL.md Adds a large “fix-issue” skill definition/workflow document.
.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.sh Adds a script to post/update an “AI Summary” PR comment from local state files.
.github/skills/ai-summary-comment/SKILL.md Adds documentation for the AI summary comment posting skill.

Comment on lines +229 to +234
// Only attempt compression when the request includes Accept-Encoding.
// The Vary header is always added above for correct caching behavior.
if (_allowCompression)
{
// Can't use += as StringValues does not override operator+
// and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507
headers.ContentEncoding = StringValues.Concat(headers.ContentEncoding, compressionProvider.EncodingName);
headers.ContentMD5 = default; // Reset the MD5 because the content changed.
headers.ContentLength = default;
}
var compressionProvider = ResolveCompressionProvider();
if (compressionProvider != null)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Now that the middleware always installs ResponseCompressionBody (even when the request lacks Accept-Encoding), other components can call IHttpResponseBodyFeature.DisableBuffering() while _allowCompression is false. ResponseCompressionBody.DisableBuffering() currently resolves a compression provider unconditionally, which will call ResponseCompressionProvider.GetCompressionProvider() with an empty Accept-Encoding and hit the Debug.Assert("Duplicate check failed.") path + duplicate NoAcceptEncoding logging. Consider short-circuiting DisableBuffering() when _allowCompression is false so it doesn’t resolve a provider (but still delegates to _innerBodyFeature.DisableBuffering()).

Copilot uses AI. Check for mistakes.
Comment on lines 224 to 226
// Can't use += as StringValues does not override operator+
// and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507
headers.Vary = StringValues.Concat(headers.Vary, HeaderNames.AcceptEncoding);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Typo in comment: “implict conversions” should be “implicit conversions”.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
---
name: fix-issue
description: End-to-end issue fixer for dotnet/aspnetcore. Runs 5 phases — Pre-Flight, Test, Gate, Fix, Finalize — creating tests and always generating phase output files. Use when asked to "fix issue #XXXXX", "fix #XXXXX", or "work on issue #XXXXX".
---

# Fix Issue — Streamlined 5-Phase Workflow

End-to-end agent that takes a GitHub issue from investigation through to a completed PR.

**Trigger phrases:** "fix issue #XXXXX", "fix #XXXXX", "work on #XXXXX"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This PR is described as a response compression caching fix, but it also introduces a large set of new “skills” documentation/scripts/tests under .github/skills/ (fix-issue, try-fix, ai-summary-comment, etc.). This looks unrelated to the ResponseCompression change and makes the PR much harder to review/validate; consider splitting the .github/skills/ additions into a separate PR (or removing them here) so the middleware fix can be reviewed/merged independently.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +13
TRY_FIX_SCRIPT="$REPO_ROOT/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.sh"
SUMMARY_SCRIPT="$REPO_ROOT/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.sh"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

TRY_FIX_SCRIPT points at .github/skills/ai-summary-comment/scripts/post-try-fix-comment.sh, but that script is not present in this PR (the scripts dir only contains post-ai-summary-comment.sh). As written, this test will fail immediately when it tries to execute the missing script; either add the script or update the test to reference an existing entry point.

Suggested change
TRY_FIX_SCRIPT="$REPO_ROOT/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.sh"
SUMMARY_SCRIPT="$REPO_ROOT/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.sh"
SUMMARY_SCRIPT="$REPO_ROOT/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.sh"
TRY_FIX_SCRIPT="$SUMMARY_SCRIPT"

Copilot uses AI. Check for mistakes.
cd "$TEMP_DIR"
git init -q . 2>/dev/null || true

OUTPUT="$(DRY_RUN=1 bash "$TRY_FIX_SCRIPT" 99999 2>&1)" || true
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The exit code assertions here don’t work as intended: OUTPUT="$(...)" || true forces a zero status, and $? after the assignment/true won’t reflect the script’s real exit code. If you want to assert success/failure, capture $? immediately from the bash ... invocation (e.g., run the command, store $?, then store output), without masking it with || true.

Suggested change
OUTPUT="$(DRY_RUN=1 bash "$TRY_FIX_SCRIPT" 99999 2>&1)" || true
OUTPUT="$(DRY_RUN=1 bash "$TRY_FIX_SCRIPT" 99999 2>&1)"

Copilot uses AI. Check for mistakes.
# ============================================================================
assert_file_exists "fix-issue SKILL.md exists" "$SKILL_FILE"
assert_file_exists "ai-summary-comment SKILL.md exists" "$AI_SUMMARY_SKILL"
assert_file_exists "post-try-fix-comment.sh exists" "$SCRIPT_DIR/../../ai-summary-comment/scripts/post-try-fix-comment.sh"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

This test asserts that .github/skills/ai-summary-comment/scripts/post-try-fix-comment.sh exists, but that script isn’t included in the PR (only post-ai-summary-comment.sh is present). Either add the missing script or remove/update these assertions so the test reflects the actual repo contents.

Suggested change
assert_file_exists "post-try-fix-comment.sh exists" "$SCRIPT_DIR/../../ai-summary-comment/scripts/post-try-fix-comment.sh"

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +137
assert_contains "Phase 2: Gate documented" "$SKILL_FILE" "phase 2.*gate"
assert_contains "Phase 3: Fix documented" "$SKILL_FILE" "phase 3.*fix"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Several of the regex expectations here appear inconsistent with the fix-issue SKILL.md in this PR (e.g., it documents Phase 2 as “Test” and Phase 3 as “Gate”, but this test looks for “phase 2.*gate”, “phase 3.*fix”, etc.). As a result, the test will fail even if the skill definition is correct; please update the asserted patterns to match the actual phase headings/numbering used in .github/skills/fix-issue/SKILL.md.

Suggested change
assert_contains "Phase 2: Gate documented" "$SKILL_FILE" "phase 2.*gate"
assert_contains "Phase 3: Fix documented" "$SKILL_FILE" "phase 3.*fix"
assert_contains "Phase 2: Test documented" "$SKILL_FILE" "phase 2.*test"
assert_contains "Phase 3: Gate documented" "$SKILL_FILE" "phase 3.*gate"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ALWAYS include the Vary: Accept-Encoding header when response compression is enabled

2 participants