Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commitf5f21f2

fix(git): stream git-upload-pack instead of buffering the whole pack

fix(git): stream git-upload-pack instead of buffering the whole pack

serviceRpc() collected the entire subprocess stdout into memory via
.arrayBuffer() before responding, and only started reading it after
already writing+closing stdin. For a repo with any real history the
generated pack can be many MB; the child starts writing it to a
pipe with a small OS buffer well before anything drains it, and
nothing does until the process exits. Past a certain pack size this
stalls and the client sees "remote end hung up unexpectedly" mid
clone/fetch.

Reproduced directly against gluecron's own self-hosted repo: a shallow
clone succeeded, a full clone/fetch consistently died mid-transfer.

git-receive-pack (push) keeps the buffered path -- callers install a
temp pre-receive hook and delete it the instant serviceRpc resolves,
so push has to keep waiting for the subprocess to actually exit.
git-upload-pack has no such coupling, so its response now streams
straight from the subprocess stdout.
ccantynz-alt committed on July 19, 2026Parent: 2415bdf
1 file changed+340f5f21f2a74841aa0546a1fd108f460f62732119c
1 changed file+34−0
Modifiedsrc/git/protocol.ts+34−0View fileUnifiedSplit
7474 proc.stdin.write(inputBytes);
7575 proc.stdin.end();
7676
77 // git-receive-pack (push) callers install a temp pre-receive hook and
78 // delete it the instant this call resolves, so pushes must keep waiting
79 // for the subprocess to fully exit before we hand back a Response.
80 // git-upload-pack (clone/fetch) has no such coupling, and buffering its
81 // stdout via `.arrayBuffer()` was the bug: for a repo with any real
82 // history the pack can be many MB, the child starts writing it to a
83 // pipe with a small OS buffer well before we ever start reading, and
84 // nothing drains stdout until the process exits -- on large enough
85 // packs this deadlocks and the client sees the connection die mid-clone
86 // (reproduced directly against this repo: shallow clone fine, full
87 // clone/fetch "remote end hung up unexpectedly"). Stream it straight
88 // through instead so the OS pipe stays drained the whole time.
89 if (service === "git-upload-pack") {
90 proc.exited.then((code) => {
91 if (code !== 0) {
92 new Response(proc.stderr)
93 .text()
94 .then((stderr) =>
95 console.error(
96 `[git] ${service} exited ${code} for ${owner}/${repo}: ${stderr}`
97 )
98 )
99 .catch(() => {});
100 }
101 });
102
103 return new Response(proc.stdout, {
104 headers: {
105 "Content-Type": `application/x-${service}-result`,
106 "Cache-Control": "no-cache",
107 },
108 });
109 }
110
77111 const stdout = await new Response(proc.stdout).arrayBuffer();
78112 await proc.exited;
79113
80114