| 1 | # 0014 — A flush-only POST is a probe, not a malformed push |
| 2 | |
| 3 | **Status:** adopted — fixes a bug that only appears above 1 MiB |
| 4 | |
| 5 | ## Decision |
| 6 | |
| 7 | `GitService.receivePack` answers a request whose entire body is a single flush |
| 8 | packet (`0000`) with `200` and an empty body. It does not parse it, touch the |
| 9 | log, or run `receive-pack`. |
| 10 | |
| 11 | ## Why git sends it |
| 12 | |
| 13 | A push larger than `http.postBuffer` (default 1 MiB) cannot be buffered, so git |
| 14 | sends it with `Transfer-Encoding: chunked`. A chunked request cannot be rewound, |
| 15 | which means git cannot replay it after a `401` to add credentials. |
| 16 | |
| 17 | So `probe_rpc()` in git's `remote-curl.c` fires a throwaway POST first — body |
| 18 | exactly `0000`, `Content-Length: 4` — checks the status, handles any auth |
| 19 | challenge there, and only then streams the real request. |
| 20 | |
| 21 | Captured from a real push: |
| 22 | |
| 23 | ``` |
| 24 | POST #1 bytes=4 content-length=4 head="0000" |
| 25 | POST #2 bytes=2428943 transfer-encoding=chunked head="00b600000000" |
| 26 | ``` |
| 27 | |
| 28 | ## The symptom |
| 29 | |
| 30 | Answering the probe with `400` kills the push before the real body is sent: |
| 31 | |
| 32 | ``` |
| 33 | error: RPC failed; HTTP 400 curl 22 The requested URL returned error: 400 |
| 34 | send-pack: unexpected disconnect while reading sideband packet |
| 35 | fatal: the remote end hung up unexpectedly |
| 36 | ``` |
| 37 | |
| 38 | The server-side error was `no ref update commands`, which is accurate — the |
| 39 | probe genuinely has none — and points nowhere near the cause. |
| 40 | |
| 41 | It reproduces **only above `http.postBuffer`**. Pushes of 95 KB and 178 KB use |
| 42 | `Content-Length` and never probe, so the whole test suite and two rounds of |
| 43 | manual probing missed it. Any repository of real size hits it every time. |
| 44 | |
| 45 | ## Consequence for authentication |
| 46 | |
| 47 | The probe is where git expects to be challenged. When the Basic auth guard lands |
| 48 | ([0011](0011-deferred.md)), it must run on the probe and return `401` with |
| 49 | `WWW-Authenticate: Basic realm="Ghost"` — that is the whole reason the probe |
| 50 | exists. Skipping auth for flush-only bodies would defeat it, so the probe |
| 51 | short-circuit sits *after* guards, inside the handler, not in middleware. |
| 52 | |
| 53 | ## Note on `upload-pack` |
| 54 | |
| 55 | `probe_rpc` is not push-specific; a fetch with a very large want/have list would |
| 56 | probe too. That path needs no special case: `git upload-pack --stateless-rpc` |
| 57 | given a lone flush packet exits cleanly with empty output, which is already a |
| 58 | valid `200`. |