Cloning & pushing
The host speaks git’s smart HTTP protocol. Standard clients work unmodified, with no extensions and no wrapper CLI:
git clone "https://$HOST/acme/web.git"Credentials
Section titled “Credentials”git sends HTTP Basic. The credential goes in the password field of
the remote URL and the username is ignored:
git remote add origin "https://x:$TOKEN@$HOST/acme/web.git"What $TOKEN is depends on the middleware the host runs.
The starter secret
Section titled “The starter secret”The host from Getting Started accepts one
secret, an Alchemy.Random the stack outputs. alchemy deploy prints
it:
bun alchemy deploy# { url: "https://…workers.dev", secret: "9f2c…" }export TOKEN=9f2c…A Better Auth API key
Section titled “A Better Auth API key”The host from Part 4 verifies API keys. A signed-in user mints one; it is shown once:
curl -s -b jar -X POST "$HOST/api/auth/api-key/create" \ -H "Origin: $HOST" -H "Content-Type: application/json" \ -d '{"name":"laptop"}' | jq -r .keyThe example app has a settings page that does the same.
Your own
Section titled “Your own”The token is whatever your middleware accepts. The one Part 4 put in front of the host decodes the Basic password and asks Better Auth whose key it is:
const { password } = yield* HttpApiBuilder.securityDecode( HttpApiSecurity.basic,);const key = Redacted.value(password);if (key !== "") { const verified = yield* auth.api.verifyApiKey({ body: { key } }); return verified.valid && verified.key ? { id: verified.key.referenceId } : undefined;}A token issued by anything else is verified the same way: read it from the request, run the route or refuse. Auth has the pattern.
Keeping it out of .git/config
Section titled “Keeping it out of .git/config”Leave the credential out of the URL and let a credential helper
supply it. A middleware that answers an anonymous request with 401
and WWW-Authenticate makes git prompt:
git config credential.helper osxkeychain # or store, or your managergit clone "https://$HOST/acme/web.git"Password for 'https://x@…': <paste $TOKEN>A middleware that honors public serves reads of a public repository
with no credential at all; the tutorial’s does.
What is supported
Section titled “What is supported”| Capability | Notes |
|---|---|
| Clone | served from a precomputed bundle when one is current |
| Incremental fetch | multi_ack_detailed with no-done |
| Shallow clone | --depth, including deepening later |
| Push | thin packs with offset and reference deltas |
| Atomic push | all refs move or none do |
| Force push | compare-and-swap on the old value |
| Tags | lightweight and annotated, with peeled advertisement |
| Status | report-status, report-status-v2, side-band |
Every push runs a full connectivity check. The objects it references must already exist or arrive in the same pack, so a repository is never left with dangling references.
Protocol v0 only. Clients that prefer v2 negotiate down on their own.
Large pushes
Section titled “Large pushes”Push size is not capped. The body streams through the Worker and into the blob store as it arrives, and the repository’s Durable Object receives rows rather than the pack. Peak memory is a fixed window, whatever the push size. Hasher has the path and Scaling the numbers.
Troubleshooting
Section titled “Troubleshooting”A credential prompt in a script. Set GIT_TERMINAL_PROMPT=0 so a
missing credential fails at once instead of hanging.
A push rejected with a ref message. That is the protocol working: a non-fast-forward, a stale compare-and-swap, or a read-only repository. The message names the ref.
A hung clone behind a proxy. The wire endpoints must be forwarded
verbatim, with the request body streamed and the Host header kept.
A proxy that buffers bodies breaks streaming pushes.