| 1 | # 0013 — The request body is captured before anything else can await |
| 2 | |
| 3 | **Status:** adopted — fixes a data-loss bug |
| 4 | |
| 5 | ## Decision |
| 6 | |
| 7 | `GitRawBodyMiddleware` drains the request into `req.gitRawBody` before guards, |
| 8 | pipes, or handlers run. Handlers receive `Readable.from(req.gitRawBody)`, never |
| 9 | the live socket. |
| 10 | |
| 11 | ## The bug this fixes |
| 12 | |
| 13 | `GitService.receivePack` used to materialize the cache before reading the body: |
| 14 | |
| 15 | ```ts |
| 16 | const repoDirectory = await |
| 17 | const body = await |
| 18 | ``` |
| 19 | |
| 20 | An unattended request stream loses whatever arrives while the handler is busy. |
| 21 | Git writes its command section in its own socket write, so the chunk dropped |
| 22 | during that await was exactly the ref updates. The body then began at the flush |
| 23 | packet that follows them, and the parser reported *"no ref update commands"* — |
| 24 | an error that describes the symptom perfectly and points nowhere near the cause. |
| 25 | |
| 26 | It reproduces only when the await is slow enough, which is why it appeared |
| 27 | against real object storage and never in tests. |
| 28 | |
| 29 | ## Why middleware rather than reordering |
| 30 | |
| 31 | Reordering the two lines fixes today's symptom. It does not fix the shape of the |
| 32 | bug: the moment an async guard exists — and Basic auth for git will be one |
| 33 | ([0011](0011-deferred.md |
| 34 | before the handler is even called. |
| 35 | |
| 36 | Nest middleware runs before guards. Capturing there means no async work can ever |
| 37 | sit between the socket and the first read. The reordering was kept as well; |
| 38 | correct code should not depend on middleware registration for correctness. |
| 39 | |
| 40 | ## Bonus: content encoding lands here |
| 41 | |
| 42 | Git sends `Content-Encoding: gzip` on request bodies at its discretion, and the |
| 43 | stream must be unwrapped before anything parses it — the same position in the |
| 44 | pipeline, for the same reason. `decode()` handles `gzip` and ` |
| 45 | deferred item is closed. |
| 46 | |
| 47 | ## The general rule |
| 48 | |
| 49 | **Never leave an incoming stream unattended across an `await |
| 50 | do the slow work. A stream is not a value; it is an event source that is already |
| 51 | running. |