Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

181 linesCodeBlameRaw
1---
2title: How a request is served
3description: What happens between a request reaching g1t.sh or api.g1t.sh and its answer leaving, step by step, with the deadlines, caches, rate limits and timings along the way.
4---
5
6import Aside from '../../../components/Aside.astro';
7import Diagram from '../../../components/Diagram.astro';
8
9Two Workers answer almost everything you send g1t: the site, `apps/web`
10on `g1t.sh`, and the API, `apps/api` on `api.g1t.sh` and `mcp.g1t.sh`.
11Neither keeps data. Each works out who is asking, then calls the services
12that own the answer.
13
14## The site
15
16`apps/web/workers/app.ts` is the site's entry point. It sorts each request
17before any page code runs:
18
191. A request for `g1tusercontent.com` goes to `serveUsercontent`, which
20 never reads or sets the session cookie.
212. `go get` lookups get their meta page.
223. **git over HTTPS** (paths ending in `info/refs`, `git-upload-pack` or
23 `git-receive-pack`) passes the git rate limits, then goes to `repos`
24 whole, through `env.REPOS.fetch`. The site never parses git
25 ([Git](/architecture/git/)).
264. **Package registries** (`/v2/`, `/-/npm/`, `/-/cargo/` and the others)
27 go to `packages` whole. A 404 is tried once more under the workspace's
28 current name, so a renamed workspace's old URLs keep working.
295. Everything else passes the page rate limit and reaches React Router.
30
31### A page
32
33<Diagram name="page-request" />
34
35The root route's middleware, `viewerMiddleware` in
36`apps/web/app/lib/session.server.ts`, resolves the viewer once (steps 2
37and 3). The `g1t_session` cookie is 64 hex characters, `HttpOnly`,
38`Secure` and `SameSite=Lax`, and lasts 30 days; `identity` stores only its
39SHA-256. A request can send `Authorization: Bearer` with an access token
40that has the **website** permission instead of the cookie. The same
41middleware enforces the email confirmation, workspace and Code access
42gates.
43
44Loaders then call their services together (steps 4 to 7). They use the
45typed clients in `@g1t/contracts`, built in `app/lib/services.server.ts`,
46and pass the viewer in each call's arguments. Nothing waits on a call it
47does not need.
48
49### Deadlines
50
51Each binding is wrapped by `instrumented()` in `app/lib/perf.server.ts`,
52which gives every call a deadline (`app/lib/deadline.ts`). A call that
53outlives it is aborted and throws `DeadlineExceeded`, so one slow service
54cannot hold a page forever.
55
56| Call | Deadline |
57| --- | --- |
58| A read | 15 s |
59| A write, or a read named in `SLOW_READS` | 60 s |
60| A long job, named in `LONG_CALLS` | 600 s |
61
62### Reading your own writes
63
64D1 has read replicas. The site uses D1's Sessions API with the services
65that support it (`identity`, `repos`, `work`, `search`, `billing`,
66`projects`, `deployments`) by sending an `x-d1-bookmark` header on each
67call. The latest bookmark is kept in the `g1t_d1` cookie for 5 minutes,
68and for 30 seconds after a write, reads go to the primary. You never see a
69page older than a change you just made. The service side is
70`crates/kit/src/d1.rs`; TypeScript services use
71`packages/contracts/src/d1.ts`.
72
73### The public page cache
74
75A signed-out `GET` with no cookie and no `Authorization` header, for a
76public page, can be answered from the colo's cache. It is fresh for 30
77seconds, then served stale while it refreshes in the background, for up
78to 5 minutes. Before a cached repository page is served, the site asks
79`repos` whether the repository is still public, so making it private
80takes effect at once.
81
82### Server-Timing
83
84Every page carries a `Server-Timing` header: the total, each loader, each
85service's calls and time, and the database time each service reports
86about itself (`svc`, `db` and `rpc`, from `Served` in
87`crates/kit/src/d1.rs`). A cached page says
88`cache;desc="hit, Ns old"`. Open your browser's network panel on any page
89to see where its time went.
90
91<Aside title="Where it runs">
92 Every Worker runs with placement off, in the data centre nearest the
93 visitor. `scripts/perf/placement-probe.mjs` measures the alternative,
94 and CONTRIBUTING.md's **Speed** section has the rules this design
95 follows: start calls together, add no round trips, and never wake a
96 Durable Object for a read D1 can answer.
97</Aside>
98
99## The API and MCP
100
101`apps/api` serves both `api.g1t.sh` and `mcp.g1t.sh` from one Worker; a
102host starting `mcp.` is the MCP server. It sorts each request in this
103order (`apps/api/src/lib.rs`):
104
1051. OTLP at `/otlp/v1/*` goes to `telemetry` as it is.
1062. Requests that bring their own credentials are handled before any g1t
107 token is read: the Stripe webhook, inbound integration hooks at
108 `/hooks/<id>`, deployment build jobs, the Actions toolkit and OIDC
109 issuer, a job's artifacts, cache and brokered requests, and the runner
110 protocol at `/runners/*`.
1113. Rate limits.
1124. The token.
1135. OAuth.
1146. MCP, when the host says so.
1157. The REST routes.
116
117<Diagram name="api-request" />
118
119A token is sent as `Authorization: Bearer g1t_…` (or `token g1t_…`) and
120resolved by `identity` (step 3). A wrong or expired one gets a 401 with a
121`www-authenticate` header that points at the OAuth metadata, as RFC 9728
122describes, so an MCP client can start signing in by itself.
123
124### Scopes
125
126Scopes are written `resource:level`, and `admin` includes `write`, which
127includes `read`. They are defined in `crates/contracts/src/scopes.rs`, and
128`packages/contracts/src/scopes.ts` mirrors them. The token's scopes decide
129which operations it may call (step 5), and the MCP server lists only the
130tools those scopes allow. A workflow job's token carries the job's own,
131narrower scope.
132
133### One table for every operation
134
135`apps/api/src/operations.rs` lists every operation once. The REST routes,
136the MCP tools and the OpenAPI document at
137[`/openapi.json`](https://api.g1t.sh/openapi.json) are all generated from
138it; an operation without a description does not compile. The docs'
139[API reference](/reference/api/) is built from that document, and the
140CLI's command table from the reference, so the four cannot disagree.
141
142### snake_case at the edge
143
144Inside g1t, contracts are camelCase. The API's `reply()` turns every
145answer to snake_case on the way out (`g1t_kit::wire::snake_case`), leaving
146user-named keys such as inputs, env, secrets and headers as they were
147written. Errors keep one shape, `{"error": {"code", "message"}}`, with the
148status that the failure's code maps to.
149
150### OAuth 2.1
151
152The API is the OAuth issuer. It supports authorization code with PKCE for
153public clients only, with no client secrets, and dynamic registration at
154`POST /oauth/register`, whose client id encodes the client's name and
155redirect addresses. The person approves on the site, at
156`g1t.sh/oauth/authorize`; the token endpoint, `POST /oauth/token`, asks
157`identity` to exchange and refresh. Refresh tokens rotate.
158
159### MCP
160
161The MCP server speaks streamable HTTP with no session state: each `POST`
162gets one JSON-RPC answer. It supports protocol versions 2025-06-18,
1632025-03-26 and 2024-11-05. Its tools are resources with an `action`, as
164[MCP tools](/reference/mcp/) lists.
165
166## Rate limits
167
168Every limit is a Workers rate-limit binding, and
169`packages/contracts/src/rate-limits.ts` is the table of record: a test
170checks every `wrangler.jsonc` against it.
171
172| Where | Limit | Counted by |
173| --- | --- | --- |
174| The API, without a token | 60 a minute | The client's address |
175| The API, with a token | 1,000 a minute | The token, hashed |
176| REST and MCP | Counted separately | |
177| Sandbox and runner reports | Not counted | |
178
179When the limiter itself cannot answer, the request goes through: the
180limits fail open. The [rate limits page](/reference/rate-limits/) lists
181every limit you can hit.