fix: defer writeHead to prevent Content-Length truncation in payment rewriter#161
Merged
fix: defer writeHead to prevent Content-Length truncation in payment rewriter#161
Conversation
…Length truncation @hono/node-server's responseViaCache sets Content-Length from the original body size and calls writeHead before res.end. When the payment response rewriter intercepts res.end and swaps in a larger rewritten body (with full x402/mpp challenge data), the Content-Length already on the wire reflects the original smaller body. The client reads only that many bytes, truncating the JSON and causing a parse error. Fix: defer writeHead until res.end fires, then update Content-Length to match the (potentially rewritten) body before flushing both. For SSE (res.write before res.end), the deferred writeHead flushes at the first write call — no Content-Length conflict since SSE doesn't use the responseViaCache path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests installPaymentResponseRewriter by simulating @hono/node-server's
exact call patterns:
- JSON path: writeHead(status, {Content-Length}) then end(body)
- SSE path: writeHead(status, headers) then write(chunk)... then end()
Coverage:
- Content-Length updated to match rewritten body size
- Content-Length preserved when no rewrite occurs (no challenge, non-matching body)
- writeHead(status, statusMessage, headers) three-arg form
- end() without writeHead (implicit headers)
- Buffer body handling
- Hook restoration after end()
- SSE: writeHead flushed on first write, not duplicated
- SSE: payment error chunks rewritten, normal chunks unchanged
- SSE: only error chunk rewritten in multi-chunk stream
- Content-Length === actual body byte length invariant
Also exports installPaymentResponseRewriter for direct unit testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@hono/node-server'sresponseViaCachesetsContent-Lengthfrom the original MCP response body, then callswriteHeadbeforeres.end. The payment response rewriter hooksres.endand swaps in a larger body (with full x402/mpp challenge data), butContent-Lengthis already on the wire with the original smaller size. The client only reads that many bytes → truncated JSON →SyntaxError: Unterminated string in JSON at position 257.writeHeaduntilres.endfires, then updateContent-Lengthto match the rewritten body before flushing both. For SSE transports (res.writebeforeres.end), the deferredwriteHeadflushes at the firstwritecall.Test plan
responseRewriter.test.tstests pass (13/13)dev:resource+dev:cliagainst local auth/accounts — payment challenge JSON should no longer be truncated../searchwithfile:deps) to verify SSE streaming still works🤖 Generated with Claude Code