A Repo Too Big for the Old Limit Now Streams Straight In
How a push into your own storage is carried โ opened, sent in chunks, sealed with a content hash โ proven with a real 80 MB push.
How a push into your own storage works
NAOMS can act as a git remote โ the place a repository is pushed to. In everyday use that is a hosted service somewhere on the internet; here it is your own storage, on your own machine. You point a push at it and the history goes into something you hold.
What actually travels when you push is a pack: git gathers the commits and files the other side is missing, compresses them, and hands over that one bundle rather than thousands of individual objects. A pack is a single blob of bytes, and it is as big as your history demands โ a few kilobytes for a fresh project, hundreds of megabytes for one with a long life or binary assets in it.
Between your git push and the storage sits a small helper: a program git
invokes when it sees a remote address it doesn't natively understand, whose job
is to speak both sides โ git's language on one end, the storage's on the other.
That helper is where the design decision that matters gets made: how do you hand
a large, variable-sized bundle to a service?
The answer is that you don't hand it over at all. You open a stream, send the pack in chunks, and finalize โ open, chunk, chunk, chunk, โฆ, finalize.
flowchart LR G["git push naoms://owner/repo
(an 80 MB pack)"] --> H["the NAOMS git helper:
open a stream, send it in chunks"] H --> O["open"] --> C["chunk โ chunk โ chunk"] --> F["finalize โ content hash"] F --> R["your repo's branch updates
to the pushed commit"] L["the old way: one message,
capped at 64 MB"] -. ">64 MB rejected outright" .-> G
Size stops being a single-message problem, because nothing is carried in a single message. Memory use stops depending on the size of your repository, because no one has to hold the whole pack at once. And the transfer ends with a content hash โ a digest computed over the bytes that actually arrived, used as the name of the thing. Naming data by a hash of its own content is called content addressing, and its useful property is that the name is a proof: if the bytes were truncated or corrupted in flight, the hash would not match, so "here is the hash of what landed" is a checkable statement rather than a reassurance. When the finalize step reports a hash, the push is committed to storage under exactly that identity, and your branch moves to the pushed commit.
This is the right shape for big things on principle, not just convenience. Large files were never supposed to be crammed through a small message channel โ that's the lane for coordination, not for moving your big files. Streaming a pack in is the same instinct as everything else here: the heavy bytes take the heavy-bytes path.
What was wrong: a 64 MB door
Until this week the push didn't work that way. The whole pack was carried as a single message on the control channel, and that channel has a hard ceiling of 64 MB per message โ a sensible limit for the coordination traffic it was built for, and a wall for anything else.
So NAOMS inherited a very ordinary, very annoying failure: a repository whose packed history came in over that ceiling simply couldn't fit through the door. Not slow, not degraded โ the push failed outright. And the size that triggers it is entirely outside the user's control, because it is a property of how much history you have.
How it was fixed, and how we know
This week we took the door off its hinges: the helper was cut over to the streaming triple โ open, chunk, finalize โ described above. Nothing about the push is carried monolithically any more, so the 64 MB message ceiling no longer bounds what you can push.
It would be easy to claim "big pushes work now" and move on. We don't get to do
that here, so we built an end-to-end test that does the actual thing: it
generates a genuine repository whose packed history is 80 MB โ deliberately
above the old 64 MB cap, and made of incompressible data so it can't quietly
shrink under the limit โ and runs a real git push of it, through the real
compiled helper, into a real running NAOMS.
The part we care most about is which path carried it. The test doesn't just check that the push succeeded; it checks that the streaming path fired and the old monolithic path did not โ by confirming the system logged a clean finalize with a content hash, and that the record of the push lands afterward as a single honest fact: the hash of the pack that actually arrived. If the helper had ever silently rerouted back through the old capped path, an 80 MB push would have failed outright โ so a green result is itself evidence the new path did the work. What we have proven is exactly that: a real 80 MB push through the real helper. We have not measured a ceiling above it, because there is no longer a single message whose size defines one.
Two hard problems, both solved
It's worth seeing this next to its sibling, because together they close out the genuinely difficult part of moving big things. Two problems, both now built, proven, and signed off:
- Pushing a large pack into your own storage without hitting the 64 MB wall โ the streaming doorway this post is about, proven end-to-end against a real daemon with the mechanism asserted rather than assumed.
- Pulling files across peers โ they already arrive as multi-source swarms, streamed from every reachable provider at once. That work landed and closed separately.
The heavy-bytes transport โ the part that's actually hard โ is done on both sides. A push that used to bounce now streams straight in: the wall is gone, one chunk at a time.
Written by AI agents from real project logs; owned and edited by Mujo.