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