title: GRASP-01 Git hosting audience: user
GRASP-01 Git hosting
GRASP-01 gives a relay an optional Git Smart HTTP door. A repository owner publishes the NIP-34 repository announcement and its current state on the relay; the relay serves the Git objects that belong to that accepted state. The Nostr relay remains the source of authorization and discussion events.
This document tracks the GRASP-01 specification at commit
f35b4f9a4ed2f0aaaf46926e4b1733c79e21b377,
checked on 2026-09-04. The implementation is bounded to the core service. It
The later extensions are independently opt-in: grasp02, grasp03,
grasp05 and grasp06. GRASP-02 and GRASP-06 require grasp; GRASP-03 and
GRASP-05 require GRASP-02. These switches default off, so enabling grasp
alone preserves GRASP-01 behavior. This document describes the host
implementation and does not claim an upstream audit or complete conformance.
Turn it on
The grasp feature is off unless the owner enables it. The Git repositories preset
turns on open reads and the GRASP Git door. The later flags are separately
opt-in and the dependency rules above apply. The repository still applies its
ordinary fuel, storage and event rules, and the information document lists
the exact acceptance criteria. The relay advertises GRASP-01 and, when
validated and enabled, GRASP-06 in supported_grasps. GRASP-02/03/05 remain
bounded opt-in previews pending interoperability and conformance review.
The relay templates include a GRASP template for a relay whose public purpose is hosting NIP-34 repositories. A template does not add members, bans or addresses. The owner supplies those separately when the relay's event policy requires them.
Announce a repository
The repository owner publishes a kind 30617 event with a d identifier,
clone URLs and relays URLs. An announcement that hosts a repository
here must list this service in both lists. A companion announcement
from an already recognized maintainer may omit those service tags because the
repository is already hosted here. The service URL is the relay's HTTPS Git
endpoint and its WebSocket relay URL. The identifier is percent-encoded in the
Git path:
/<npub>/<percent-encoded-identifier>.git
The relay accepts the announcement only after its normal signature, ban,
fuel, size and event policy checks. A malformed identifier, URL or maintainer
key is refused. A repository's private tag does not make a private Git door;
private repository hosting is GRASP-08 and remains outside this feature.
The accepted announcement's address is
30617:<owner-pubkey>:<identifier>. NIP-34 issues, patches, pull requests and
updates that refer to an accepted address can be stored by the normal relay
rules. GRASP-06 pull requests may arrive before the announcement: they must
still name a syntactically valid repository coordinate and remain in purgatory
until their matching Git data arrives.
Publish a state
The repository maintainer publishes a kind 30618 event with the same d
identifier, a HEAD tag and the complete refs/ and refs/ map.
The NIP-34 HEAD tag
uses ["HEAD", "ref: refs/; a bare branch path is refused.
The latest valid state is selected by NIP-34 addressable-event ordering among
the owner and the recursive maintainer set. The recursive set starts at the
owner and follows the maintainer announcements for that repository, with
bounded depth and membership limits.
The Git door accepts a normal branch or tag update only when each changed ref and the requested HEAD agree with the accepted state. Unchanged refs remain untouched, so a push does not need to repeat the entire state map. The state event therefore authorizes the bytes a push makes visible; a maintainer signature alone does not authorize an arbitrary branch. A state with no HEAD represents an unborn repository. A pending state keeps the previously materialized branch and HEAD in the Git repository, so a stock clone still checks out the prior branch. The repository moves to the new HEAD when the new tip's objects arrive.
Pull requests and purgatory
The alternative PR endpoint is /.
It is a public, empty repository namespace with at most 16 hosted namespaces
and 1,024 refs per namespace. Only refs/ refs are accepted;
ordinary branches, tags and client deletions are refused. A pre-announcement
PR must carry the exact signer PR clone URL and a valid repository coordinate
with the same identifier. The signed PR or update and its c tip authorize the
ref. Unknown events may upload first, but remain hidden until the event is
accepted; their deadline is fixed at 20 minutes. Expiry removes refs, while
immutable objects and orphaned chunks remain retained and charged because
the service has no garbage collector.
Promotion checks for relevant pending events before loading Git packs. An empty queue or another repository's pending work does not trigger that read. A retried Git write still completes any pending promotion left by an earlier successful publication; expiry and authority are checked again after reads.
A pull request or update can name a Git ref as
refs/. The event ID is checked as a 64-character lowercase
hex value. The relay can hold the event and the Git data while it waits for
the other half of the pair. Until both are present, the event is not served
from the normal read path and the response explains
purgatory: won't be served until git data arrives. Unresolved entries expire
after 30 minutes.
The relay accepts every syntactically valid refs/ push,
including one whose Nostr event has not arrived yet. It rejects a ref whose
known event names a different tip. A pending PR ref that has no accepted PR
or PR update with a matching c tag is hidden and eligible for cleanup after
20 minutes. The cleanup window limits abandoned uploads and does not implement
the broader archive or sync behavior of later GRASP documents.
Git HTTP
Your relay's Connect section includes a copyable git clone example with
its own hostname, the repository owner's npub and repository name.
Open in app opens this relay's repositories in
GitWorkshop, alongside a copyable relay URL.
Both Git connections stay hidden when Git hosting is unavailable.
The Git door is unauthenticated when the relay's reads are open. It serves
Git Smart HTTP at the repository path and returns a repository page or a 404
for a path that is not hosted. Upload-pack advertises and serves
allow-reachable-sha1-in-want, allow-tip-sha1-in-want and filter, including
blob:none and tree:0 requests. Receive-pack applies the accepted state,
maintainer and refs/ rules before storing anything.
The relay root, Git repository page and protocol responses include the GRASP
CORS headers. OPTIONS returns 204. GET and POST are the allowed methods;
Content-Type, Authorization, Git-Protocol and X-Git-Request-Id are
allowed request headers. The NIP-5A site origins remain isolated from relay
events and repository data.
The relay Durable Object's SQLite database stores compressed Git object chunks,
refs and receipts. The backend removes the former R2 per-push root and
transaction ceiling while preserving accepted-state authority, retry receipts
and purgatory behavior. It keeps bounded object, byte, ref and receipt limits,
and does not promise lower cost for every repository. SQLite storage is charged
through the existing events allowance. The Worker depends on the ntig package, whose source and package artifact
are recorded in Vendored dependencies. Its repository contract validates pack
structure, deltas, object dependencies, trees and commits before publication.
The owner can tune the Git capacity in the console's Rules → Git capacity panel,
or in an exported configuration under policy.git. Values are bytes unless
noted otherwise; omitted fields keep the current values. The editor validates
against the same maximums used by the runtime. For example, a larger personal
relay can set a 2 GiB retained Git allowance and 512 repositories:
{
"format": "bind.ws/relay-config/2",
"policy": {
"git": {
"maxRepositories": 512,
"maxRelayBytes": 2147483648,
"maxCompressedBytes": 1073741824,
"maxPackBytes": 99614720,
"maxObjectBytes": 67108864,
"maxTransactionRawBytes": 67108864
}
}
}
The defaults are:
| Scope | Limit | What it controls |
|---|---|---|
| Relay | 128 repositories, 2 GiB Git storage | accepted repositories and retained compressed Git objects plus metadata across the relay |
| Repository | 16 MiB per pack or object | one upload and one stored object |
| Repository | 4 GiB raw, 1 GiB compressed and 128 MiB metadata | retained object payloads and their SQL records |
| Repository | 4,096 refs, 100,000 objects and 1,000,000 graph edges | ref maps and verified object graphs |
| Repository | receipt metadata within the metadata limit | retry records without a separate transaction-count ceiling |
The configurable maximums are 4,096 repositories, 6 GiB relay storage, 95 MiB packs, 64 MiB objects, 64 GiB raw data, 6 GiB compressed data and 1 GiB metadata per repository. Request limits are separately capped at 64 MiB and 100,000 objects per transaction, with 6 GiB fetch responses. These ceilings protect the Worker and SQLite database; raising a relay's settings does not remove storage, CPU, bandwidth or fuel costs. The per-relay physical SQLite ceiling and backup/archive limits remain separate operational constraints.
The SQL object store reads and writes chunks, refs and receipts in native SQL
transactions. Clone and fetch stream selected objects from the index. Pushes remain bounded
by the per-request pack and decoded-byte budgets; a large initial history may
need staged pushes. Fetch currently sends complete requested reachable history,
so incremental fetches may transfer objects the client already has. Compaction, orphan collection and
production capacity measurements remain follow-up work. Large repositories
should use ordinary client-side git clone --mirror or git bundle recovery;
the portable relay backup is intentionally bounded and may omit Git payloads.
Expired unknown
PR refs stay hidden when cleanup cannot commit, while immutable object history
keeps its bytes until the relay is torn down.
Git HTTP walks indexed object metadata to check reachability and filters before loading selected object bodies. Every write rechecks authority inside the SQL transaction, which publishes objects, refs, receipts and pending-event visibility together. The relay admits one repository operation at a time.
The owner can request gitstorage <repository-owner-hex> <identifier> through
the management API to inspect the repository's object, ref and receipt totals.
The report includes compressed payload, raw object and metadata bytes plus the
relay's complete SQLite size. The diagnostic does not change stored objects,
refs or receipts.
Reports read indexed SQL totals with a 60-second cooldown per live relay instance. A restart resets that cooldown. Git requests, event writes and configuration changes receive a retry response while a scan owns the relay. See HTTP reference for the method and Costs and margins for its host costs and limits.
Portable relay backups include compressed Git objects, refs and retry receipts within the archive's 8 MiB and 12,000-entry limits. The owner can request a configuration/events/media backup with Git omitted when repositories are too large for this bounded format. Restore verifies repository identity, object hashes, dependency graphs and the receipt chain before writing the fresh target. See Data and names.
Proactive synchronization and archive
With grasp02, the relay performs repository-scoped historic and bounded live
event synchronization from accepted announcement relay lists. Missing Git data
is queued from signed state and accepted PR/update clone sources with an hourly
target subject to queued work, fuel and size limits. At most 16 Git sources are
considered; each connected round is bounded to 10 seconds. Failures retry after
five minutes before returning to the hourly schedule. Packs use policy.git.maxPackBytes (16 MiB by default), with 1 MiB of
additional wire response allowance. The same configured object and transaction
bounds apply to remote verification. Remote
advertisements never authorize refs; signed state and accepted events are
rechecked under the repository fence. PR objects already uploaded to this
relay through GRASP-06 are copied locally into the target repository, without
a self-fetch. Multiple clone URLs in a single tag are considered.
History uses durable cursors with inclusive timestamp overlap and a 256-event page cap. A source page cap is surfaced as partial history rather than silently reported complete. Up to 512 durable sync jobs and 128 history windows are retained. Restarts resume unfinished history before following the bounded 1.5 second live tail.
With grasp03, accepted issue, patch and PR threads are expanded through the
authors' kind 10002 write relays. Replies may use #e or #E and do not need
a new repository coordinate; the accepted parent thread remains the scope
boundary. Only write relays from kind 10002 outbox lists are used. Metadata
refresh is limited to kinds 0, 10002 and 10317, with the same bounded source
and job limits.
With grasp05, the relay may admit announcements that omit its own clone and
relay URLs. Archive admission still consumes the normal repository quota and
storage accounting, and depends on GRASP-02. Immutable packs remain retained
and charged; expiry does not provide garbage collection.
The owner can inspect synchronization through storagestats. Its
graspSync result records whether synchronization is enabled, whether source
or scope caps made the result partial, event and Git job counts, durable error
counts, unfinished history windows, and the earliest due Unix timestamp (or
null). Error details remain in durable SQL rows. Backups preserve optional
state.prRefs rows, including until: 0 tombstones for accepted or deleted
events; backups made before this field was added remain readable.
Information document and limits
When GRASP is on and reads are open, NIP-11 includes:
| Field | Meaning |
|---|---|
supported_grasps |
Lists only locally validated and enabled profiles; dependencies still apply. |
repo_acceptance_criteria |
who may announce repositories under the current write and kind rules, guest exceptions, required relay listing and storage limits |
curation |
omitted unless the owner applies curation beyond generic spam, bans or fuel rules |
GitWorkshop classifies the acceptance description as public or approval required. Open writes allow anyone to announce a repository; allowlist, owner-only and web-of-trust writes describe their eligible authors. A guest exception for kind 30617 opens announcements, while state and collaboration events still follow their own rules. A blocked announcement kind preserves the owner's existing kind-rule exemption. Changing a rule changes the description without granting any new permission.
NIP-11 never claims GRASP-08. A relay's regular read rule still governs the Nostr side, and turning GRASP off removes its Git door and advertisement without changing the NIP-5A site origins.
Limits under load
One Git operation runs at a time for a relay. The operation fence also blocks relay-side event and control mutations while a Git transaction reads or writes its object graph. This bounds Worker memory and keeps the SQL object snapshot and the relay's authority view from changing underneath one operation.
Git requests consume the relay's normal address rate limits. A busy fence or
rate limit answers 429 with a retry reason. Fuel exhaustion, an unclaimed
relay, a restricted read rule or a repository limit answers a restricted:
response. A caller retries after the stated condition clears; it does not
assume that a rejected receive-pack changed refs.
What this does not promise
The preview extensions do not promise complete history, uninterrupted live connections, unconditional hourly Git work, private repositories, or an upstream conformance result. Work is bounded by queued jobs, fuel, source, window, byte and storage limits. The repository's Git object store is an implementation detail; clients rely on the NIP-34 events and GRASP HTTP contract, not on a particular backend.
See Your relay on the web for the user-facing site and domain doors, Architecture for the Worker and Durable Object boundaries, and HTTP reference for the exact request paths and statuses.