Skip to content

fix: preserve request body in debug transport#671

Merged
omehegan merged 3 commits intomainfrom
fix/debug-transport-body-drain
Mar 2, 2026
Merged

fix: preserve request body in debug transport#671
omehegan merged 3 commits intomainfrom
fix/debug-transport-body-drain

Conversation

@omehegan
Copy link
Contributor

@omehegan omehegan commented Mar 2, 2026

Description

The --debug flag was causing a 400 error from CloudFront, rather than giving me any debug output:

❯ bk pipeline create "CLI testing" --description "Testing CLI arguments" --repository "git@github.com:omehegan/bk-testproject.git" --debug
...
DEBUG response uri=https://api.buildkite.com/v2/organizations/owen-mehegan-1/pipelines
HTTP/2.0 400 Bad Request
Content-Length: 903
Content-Type: text/html
Date: Mon, 02 Mar 2026 02:36:00 GMT
Server: CloudFront
Via: 1.1 6f01da654ce062002b93fece7e4b0d9e.cloudfront.net (CloudFront)
X-Amz-Cf-Id: CBg75gMcb6XnElTsC_fgI3x_-xfySqzKtLvEMVHdxidagNeT33zPfg==
X-Amz-Cf-Pop: MEL51-P2
X-Cache: Error from cloudfront

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>400 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">

We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.

The debugTransport.RoundTrip method used req.Clone() to create a copy for debug logging, but Clone() performs a shallow copy — the Body io.ReadCloser is shared between the original and clone. When httputil.DumpRequestOut read the clone's body for logging, it drained the original request's body too. The subsequent RoundTrip then sent a request with an empty body but the original Content-Length header, causing CloudFront to reject it with a 400 error.

Fix: Read the body bytes into memory once, then create independent bytes.Reader instances for both the debug dump and the real request. This ensures the body is fully available for both logging and the actual HTTP call.

Testing

  • Tests have run locally (with go test ./...)
  • Code is formatted (with go fmt ./...)

Disclosures / Credits

100% Claude. I noticed the issue and couldn't figure out what was going on. While debugging with Claude, it just went ahead and produced a fix. I verified that it works, it's formatted, and tests pass. I also asked it to go back and check that its changes conform to the general coding style of the repo. Then I got it to give me sort of a "Go newbie" explanation of what was going on in the bug - so as I understand it, we were losing the request body when we logged it to console in the debug output, and thus sending a zero-byte body in the request (only impacting commands that HAVE request a body, like create where I stumbled on it).

The debugTransport.RoundTrip method used req.Clone() to create a copy
for debug logging, but Clone() performs a shallow copy — the Body
io.ReadCloser is shared between the original and clone. When
httputil.DumpRequestOut read the clone's body for logging, it drained
the original request's body too. The subsequent RoundTrip then sent a
request with an empty body but the original Content-Length header,
causing CloudFront to reject it with a 400 error.

Fix: read the body bytes upfront and create independent bytes.Reader
instances for both the debug dump and the real request.
@omehegan omehegan requested a review from a team as a code owner March 2, 2026 02:47
@omehegan omehegan merged commit aeb36bb into main Mar 2, 2026
1 check passed
@omehegan omehegan deleted the fix/debug-transport-body-drain branch March 2, 2026 23:02
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