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.

g1t.sh
290 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version1//! Repositories that are archived, deleted or purged, and branches
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.2//! renamed: what issues, pull requests and runs do about each.
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version3//!
4//! An archived repository is read-only: its issues and pull requests are
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.5//! locked and nothing new starts on it. A deleted one looks missing (repos
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version6//! hides it) and keeps its rows for a restore. A purged one is gone, and
7//! every row kept for it goes with it.
8
9use g1t_contracts::events::{BranchRenamed, Event, RepoArchived, RepoDeleted};
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.10use g1t_contracts::repos::{Repo, RepoStatus, StatusByIdArgs};
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version11use g1t_contracts::{FailureCode, Outcome};
12use g1t_contracts::time::rfc3339;
13use g1t_kit::now_ms;
14use serde::{Deserialize, Serialize};
15use worker::Result;
16
17use crate::Work;
18
19/// `Ok` when `repo` may be changed; refused, saying why, when it is
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.20/// archived.
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version21pub(crate) fn writable(repo: &Repo) -> Outcome<()> {
Merge branch 'mirroring' into artifacts-mode22 match repo.read_only_reason() {
23 Some(reason) => Outcome::fail(FailureCode::Forbidden, reason),
24 None => Outcome::Ok(()),
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version25 }
26}
27
28/// `repo` as found, unless it is archived: then refused, as [`writable`]
29/// says. For the steps g1t starts by itself, which stop on a refusal.
30pub(crate) fn unless_archived(repo: Outcome<Repo>) -> Outcome<Repo> {
31 match repo {
32 Outcome::Ok(repo) => match writable(&repo) {
33 Outcome::Ok(()) => Outcome::Ok(repo),
34 Outcome::Fail(failure) => Outcome::Fail(failure),
35 },
36 failed => failed,
37 }
38}
39
40/// `repo.purged`: every row kept for the repository, `?1` its id. Rows
41/// kept by pull request go first, while the pull requests still name them.
42pub(crate) const PURGED: &[&str] = &[
43 "DELETE FROM session_entries WHERE pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
The work service coordinates work on a repository: sessions of people's own harnesses (start is idempotent on the harness's session id, every update or batch of entries is a heartbeat that renews the session's claims, a session reads as idle after 15 minutes and ends as timed_out after 2 hours), session entries recorded once per key and shown in the pull request's session when the session is on one (session_entries is rebuilt so an entry can belong to a session alone), advisory expiring claims on issues that are never locks (claiming tells you who else is on it and what the issue is blocked by; release, hand-off with a note, and expiry noticed on the next read), a writer for blocked_by on update_issue that refuses other repositories' numbers, the issue itself and cycles, and get_work, which gathers an issue's definition of done, claims, sessions, pull requests with their checks, mergeability, overlaps and queue place, blockers, the issues it blocks, decisions and recent coordination events. It publishes session.started, session.ended, work.claimed, work.released, work.claim_expired, work.handoff_requested, dependency.changed (when blocked_by changes, and when a blocker closes or has its pull request merged) and overlap.detected (once for each side when a push or a new pull request starts an overlap, again only after it cleared), and the contracts and the TypeScript mirror list them.44 "DELETE FROM session_entries WHERE session_id IN (SELECT id FROM sessions WHERE repo_id = ?1)",
45 "DELETE FROM sessions WHERE repo_id = ?1",
46 "DELETE FROM claims WHERE repo_id = ?1",
47 "DELETE FROM overlap_pairs WHERE repo_id = ?1",
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version48 "DELETE FROM check_runs WHERE pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
49 "DELETE FROM review_runs WHERE pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
50 "DELETE FROM agent_messages WHERE repo_id = ?1 OR pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
51 "DELETE FROM queue_entries WHERE repo_id = ?1",
52 "DELETE FROM agent_mentions WHERE repo_id = ?1",
53 "DELETE FROM agent_rules WHERE repo_id = ?1",
54 "DELETE FROM pull_confidence WHERE repo_id = ?1",
55 "DELETE FROM run_confidence WHERE repo_id = ?1",
56 "DELETE FROM confidence_rules WHERE repo_id = ?1",
57 "DELETE FROM agent_runs WHERE repo_id = ?1",
58 "DELETE FROM commit_statuses WHERE repo_id = ?1",
Merge checks: statuses and check runs on every commit59 "DELETE FROM commit_check_annotations WHERE run_id IN (SELECT id FROM commit_check_runs WHERE repo_id = ?1)",
60 "DELETE FROM commit_check_runs WHERE repo_id = ?1",
61 "DELETE FROM commit_check_suites WHERE repo_id = ?1",
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version62 "DELETE FROM plans WHERE repo_id = ?1",
63 "DELETE FROM comments WHERE repo_id = ?1",
64 "DELETE FROM pulls WHERE repo_id = ?1",
65 "DELETE FROM issues WHERE repo_id = ?1",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar66 "DELETE FROM labels WHERE repo_id = ?1",
67 "DELETE FROM milestones WHERE repo_id = ?1",
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version68 "DELETE FROM counters WHERE repo_id = ?1",
69 "DELETE FROM repo_settings WHERE repo_id = ?1",
70 "DELETE FROM memories WHERE scope = 'project' AND scope_key = ?1",
71 "DELETE FROM guardrails WHERE scope <> 'workspace' AND scope_key = ?1",
72];
73
74/// What a run stopped because its repository was archived or deleted says
75/// as its last step; [`Work::runs_in_repo`] finds such runs by it.
76const STOPPED_STEP: &str = "Stopped: the repository was archived or deleted.";
77
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.78/// `repo.deleted` and `repo.archived`: the repository's runs that
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version79/// are queued or running stop, `?1` the step they end on, `?2` the time,
80/// `?3` its id.
81const STOP_RUNS: &str = "UPDATE agent_runs SET status = 'stopped', step = ?1, finished_at = ?2, updated_at = ?2
82 WHERE repo_id = ?3 AND status IN ('queued', 'running')";
83
84/// `runs_in_repo` (internal, for the runner): the agent runs of a
85/// repository whose sandboxes should be stopped: those queued or running,
86/// and those work stopped in the last hour because the repository was
87/// archived or deleted. Takes `{ "repoId" }`; returns `[RepoRun]`.
88#[derive(Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub(crate) struct RunsInRepoArgs {
91 pub repo_id: String,
92}
93
94#[derive(Debug, Deserialize, Serialize)]
95#[serde(rename_all = "camelCase")]
96pub(crate) struct RepoRun {
97 pub run_id: String,
98 pub sandbox: String,
99 pub kind: String,
100 pub status: String,
101 pub pull_id: Option<String>,
102}
103
104/// The repository, by id and path, that `event` says was deleted or
105/// archived (not unarchived): its runs stop.
106fn stopping(event: &Event) -> Option<(String, String, String)> {
107 let data = event.data.clone();
108 match event.kind.as_str() {
109 "repo.deleted" => serde_json::from_value::<RepoDeleted>(data).ok().map(|e| (e.repo_id, e.namespace, e.name)),
110 "repo.archived" => serde_json::from_value::<RepoArchived>(data)
111 .ok()
112 .filter(|e| e.archived)
113 .map(|e| (e.repo_id, e.namespace, e.name)),
114 _ => None,
115 }
116}
117
118/// `branch.renamed`: open pull requests from `?2` in repository `?1`
119/// come from `?3` now. A fork carries its change on its own default
120/// branch, so only branches of the repository itself are named.
121const BRANCH_RENAMED: &str = "UPDATE pulls SET source_branch = ?3
122 WHERE repo_id = ?1 AND source_branch = ?2 AND fork_repo_id IS NULL
123 AND status IN ('draft', 'open')";
124
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar125/// `branch.renamed`: open pull requests into `?2` merge into `?3` now.
126/// Those into the default branch name none, and follow it as they are.
127const BASE_RENAMED: &str = "UPDATE pulls SET base_branch = ?3
128 WHERE repo_id = ?1 AND base_branch = ?2 AND status IN ('draft', 'open')";
129
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version130impl Work {
131 /// Whether work may start on the repository: neither archived nor
132 /// deleted. When repos cannot say, it is taken as active, and the
133 /// repos calls that follow (which hide deleted repositories) decide.
134 pub(crate) async fn repo_active(&self, repo_id: &str) -> Result<bool> {
135 let status: Result<RepoStatus> = g1t_kit::call(
136 &self.repos,
137 "status_by_id",
138 &StatusByIdArgs {
139 id: repo_id.to_owned(),
140 },
141 )
142 .await;
143 Ok(match status {
144 Ok(status) => status.active(),
145 Err(error) => {
146 worker::console_error!("status_by_id {repo_id}: {error}");
147 true
148 }
149 })
150 }
151
152 /// See [`RunsInRepoArgs`].
153 pub(crate) async fn runs_in_repo(&self, a: RunsInRepoArgs) -> Result<Vec<RepoRun>> {
154 let hour_ago = rfc3339(now_ms().saturating_sub(60 * 60 * 1000));
155 self.db
156 .prepare(
157 "SELECT id AS run_id, sandbox, kind, status, pull_id FROM agent_runs
158 WHERE repo_id = ?1 AND (status IN ('queued', 'running')
159 OR (status = 'stopped' AND step = ?2 AND finished_at >= ?3))
160 ORDER BY created_at LIMIT 200",
161 )
162 .bind(&[a.repo_id.as_str().into(), STOPPED_STEP.into(), hour_ago.as_str().into()])?
163 .all()
164 .await?
165 .results::<RepoRun>()
166 }
167
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.168 /// `repo.deleted` and `repo.archived`: its runs stop (the runner stops
169 /// their sandboxes, see [`RunsInRepoArgs`]). Rows are kept, for a restore.
170 async fn stop_runs_in(&self, repo_id: &str) -> Result<()> {
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version171 let now = rfc3339(now_ms());
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.172 self.db
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version173 .prepare(STOP_RUNS)
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.174 .bind(&[STOPPED_STEP.into(), now.as_str().into(), repo_id.into()])?
175 .run()
176 .await?;
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version177 Ok(())
178 }
179
180 /// `repo.purged`, `repo.deleted`, `repo.archived` and `branch.renamed`.
181 /// Says whether `event` was one this handles completely.
182 pub(crate) async fn on_retired(&self, event: &Event) -> Result<bool> {
183 if g1t_kit::lifecycle::on_purged(&self.db, event, PURGED).await? {
184 return Ok(true);
185 }
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.186 if let Some((repo_id, _, _)) = stopping(event) {
187 self.stop_runs_in(&repo_id).await?;
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version188 return Ok(true);
189 }
190 if event.kind != "branch.renamed" {
191 return Ok(false);
192 }
193 let Ok(renamed) = serde_json::from_value::<BranchRenamed>(event.data.clone()) else {
194 worker::console_error!("branch.renamed {} could not be read", event.id);
195 return Ok(true);
196 };
197 if renamed.from == renamed.to {
198 return Ok(true);
199 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar200 let names = [
201 renamed.repo_id.as_str().into(),
202 renamed.from.as_str().into(),
203 renamed.to.as_str().into(),
204 ];
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version205 self.db
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar206 .batch(vec![
207 self.db.prepare(BRANCH_RENAMED).bind(&names)?,
208 self.db.prepare(BASE_RENAMED).bind(&names)?,
209 ])
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version210 .await?;
211 Ok(true)
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn repo(archived_at: Option<&str>) -> Repo {
220 Repo {
221 id: "rep_1".into(),
222 namespace: "acme".into(),
223 name: "web".into(),
224 description: None,
225 is_private: false,
226 owner_id: "usr_1".into(),
227 default_branch: "main".into(),
228 fork_of: None,
229 protected: false,
230 created_at: "2026-10-05T00:00:00Z".into(),
231 topics: Vec::new(),
232 website: None,
233 archived_at: archived_at.map(str::to_owned),
234 }
235 }
236
237 #[test]
238 fn an_archived_repository_refuses_changes() {
239 assert!(matches!(writable(&repo(None)), Outcome::Ok(())));
240 match writable(&repo(Some("2026-10-05T00:00:00Z"))) {
241 Outcome::Fail(failure) => {
242 assert_eq!(failure.code, FailureCode::Forbidden);
243 assert!(failure.message.contains("acme/web is archived"));
244 }
245 Outcome::Ok(()) => panic!("an archived repository was writable"),
246 }
247 }
248
249 #[test]
250 fn purging_takes_only_the_repository_id() {
251 for sql in PURGED {
252 assert_eq!(g1t_kit::transfer::parameters(sql), 1, "{sql}");
253 }
254 // Rows kept by pull request go before the pull requests do.
255 let pulls = PURGED.iter().position(|sql| sql.starts_with("DELETE FROM pulls")).unwrap();
256 for table in ["session_entries", "check_runs", "review_runs", "agent_messages"] {
257 let at = PURGED.iter().position(|sql| sql.contains(&format!("FROM {table} "))).unwrap();
258 assert!(at < pulls, "{table}");
259 }
260 }
261
262 fn event(kind: &str, data: serde_json::Value) -> Event {
263 Event {
264 id: "evt_1".into(),
265 kind: kind.into(),
266 source: "repos".into(),
267 time: "2026-10-05T00:00:00Z".into(),
268 repo_id: Some("rep_1".into()),
269 actor: None,
270 data,
271 }
272 }
273
274 #[test]
275 fn deleting_or_archiving_stops_runs_and_unarchiving_does_not() {
276 let named = Some(("rep_1".to_owned(), "acme".to_owned(), "web".to_owned()));
277 let data = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": true });
278 assert_eq!(stopping(&event("repo.deleted", data.clone())), named);
279 assert_eq!(stopping(&event("repo.archived", data)), named);
280 let unarchived = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": false });
281 assert_eq!(stopping(&event("repo.unarchived", unarchived.clone())), None);
282 assert_eq!(stopping(&event("repo.archived", unarchived)), None);
283 assert_eq!(STOP_RUNS.matches('?').count(), 4);
284 }
285
286 #[test]
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.287 fn renamed_branches_bind_three_names() {
Cargo on g1t.sh: a sparse registry per workspace, cargo publish/add/yank/search with a g1t token; npm rows show their version288 assert_eq!(g1t_kit::transfer::parameters(BRANCH_RENAMED), 3);
289 }
290}

This file's history is long; its oldest lines are credited to the oldest commit read.