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
293 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.

Inbox: the events service tells people what needs them as events arrive1//! 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.
Inbox: the events service tells people what needs them as events arrive3//!
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
Inbox: the events service tells people what needs them as events arrive6//! 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};
Inbox: the events service tells people what needs them as events arrive11use 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.
Inbox: the events service tells people what needs them as events arrive21pub(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(()),
Inbox: the events service tells people what needs them as events arrive25 }
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)",
Merge branch 'worktree-agent-a6ec0a89d7fe7a9ae'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",
Inbox: the events service tells people what needs them as events arrive48 "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",
Merge branch 'worktree-agent-ab8033ab973f8d99a'52 "DELETE FROM queue_probes WHERE repo_id = ?1",
53 "DELETE FROM decisions WHERE repo_id = ?1",
Inbox: the events service tells people what needs them as events arrive54 "DELETE FROM agent_mentions WHERE repo_id = ?1",
55 "DELETE FROM agent_rules WHERE repo_id = ?1",
56 "DELETE FROM pull_confidence WHERE repo_id = ?1",
57 "DELETE FROM run_confidence WHERE repo_id = ?1",
58 "DELETE FROM confidence_rules WHERE repo_id = ?1",
59 "DELETE FROM agent_runs WHERE repo_id = ?1",
60 "DELETE FROM commit_statuses WHERE repo_id = ?1",
Merge checks: statuses and check runs on every commit61 "DELETE FROM commit_check_annotations WHERE run_id IN (SELECT id FROM commit_check_runs WHERE repo_id = ?1)",
62 "DELETE FROM commit_check_runs WHERE repo_id = ?1",
63 "DELETE FROM commit_check_suites WHERE repo_id = ?1",
Inbox: the events service tells people what needs them as events arrive64 "DELETE FROM plans WHERE repo_id = ?1",
65 "DELETE FROM comments WHERE repo_id = ?1",
66 "DELETE FROM pulls WHERE repo_id = ?1",
67 "DELETE FROM issues WHERE repo_id = ?1",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar68 "DELETE FROM labels WHERE repo_id = ?1",
69 "DELETE FROM milestones WHERE repo_id = ?1",
Inbox: the events service tells people what needs them as events arrive70 "DELETE FROM counters WHERE repo_id = ?1",
71 "DELETE FROM repo_settings WHERE repo_id = ?1",
Memory is gone: people's own harnesses keep their own memory (CLAUDE.md, AGENTS.md, auto-memory), so g1t's project and workspace notes that agents recalled and remembered only duplicated them. The recall and remember operations, their /repos/{owner}/{name}/memory routes, the memory MCP tool and its lines in the MCP instructions, the Memory section of the API reference and OpenAPI document (and the CLI's table generated from it) are removed, as are the work service's memory methods, the Memory pages of a project and of a workspace with their sidebar links, Home's Decisions card (which read decisions out of workspace memory), the Memory guide (its address and the old agent-memory, agents-and-memory and context-hub ones now lead to Connect a coding agent) and every mention in the docs, README, llms.txt, the landing page, the privacy policy and the Ask bench. Decision records, g1t why and g1t work stay as they were. memory:read and memory:write are retired as agents:run and the models scopes were: tokens and OAuth grants that hold them still parse and keep working for their other scopes, but no preset (the Agent preset and the OAuth default included), token form, mobile sign-in or hosted session hands them out and nothing needs them; looking up a ticket (search ticket, get_context), which needed memory:read, needs issues:read. The memories table stays in work's D1 untouched and is no longer read or written, except that purging a repository or deleting a workspace still deletes its old rows. Checked: cargo test for g1t-contracts (308), g1t-api (201), g1t (102 and 41), g1t-work (103) and g1t-integrations (65), cargo check of the workspace, the OpenAPI document and ops table regenerated, every workspace's typecheck, the site's 901 tests and a production build, the contracts' 48 tests, the phone app's 11, the deploy tool's 92, and the docs' tests and build.72 // Notes kept when g1t had project memory, no longer read or written.
Inbox: the events service tells people what needs them as events arrive73 "DELETE FROM memories WHERE scope = 'project' AND scope_key = ?1",
74 "DELETE FROM guardrails WHERE scope <> 'workspace' AND scope_key = ?1",
75];
76
77/// What a run stopped because its repository was archived or deleted says
78/// as its last step; [`Work::runs_in_repo`] finds such runs by it.
79const STOPPED_STEP: &str = "Stopped: the repository was archived or deleted.";
80
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.81/// `repo.deleted` and `repo.archived`: the repository's runs that
Inbox: the events service tells people what needs them as events arrive82/// are queued or running stop, `?1` the step they end on, `?2` the time,
83/// `?3` its id.
84const STOP_RUNS: &str = "UPDATE agent_runs SET status = 'stopped', step = ?1, finished_at = ?2, updated_at = ?2
85 WHERE repo_id = ?3 AND status IN ('queued', 'running')";
86
87/// `runs_in_repo` (internal, for the runner): the agent runs of a
88/// repository whose sandboxes should be stopped: those queued or running,
89/// and those work stopped in the last hour because the repository was
90/// archived or deleted. Takes `{ "repoId" }`; returns `[RepoRun]`.
91#[derive(Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub(crate) struct RunsInRepoArgs {
94 pub repo_id: String,
95}
96
97#[derive(Debug, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase")]
99pub(crate) struct RepoRun {
100 pub run_id: String,
101 pub sandbox: String,
102 pub kind: String,
103 pub status: String,
104 pub pull_id: Option<String>,
105}
106
107/// The repository, by id and path, that `event` says was deleted or
108/// archived (not unarchived): its runs stop.
109fn stopping(event: &Event) -> Option<(String, String, String)> {
110 let data = event.data.clone();
111 match event.kind.as_str() {
112 "repo.deleted" => serde_json::from_value::<RepoDeleted>(data).ok().map(|e| (e.repo_id, e.namespace, e.name)),
113 "repo.archived" => serde_json::from_value::<RepoArchived>(data)
114 .ok()
115 .filter(|e| e.archived)
116 .map(|e| (e.repo_id, e.namespace, e.name)),
117 _ => None,
118 }
119}
120
121/// `branch.renamed`: open pull requests from `?2` in repository `?1`
122/// come from `?3` now. A fork carries its change on its own default
123/// branch, so only branches of the repository itself are named.
124const BRANCH_RENAMED: &str = "UPDATE pulls SET source_branch = ?3
125 WHERE repo_id = ?1 AND source_branch = ?2 AND fork_repo_id IS NULL
126 AND status IN ('draft', 'open')";
127
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar128/// `branch.renamed`: open pull requests into `?2` merge into `?3` now.
129/// Those into the default branch name none, and follow it as they are.
130const BASE_RENAMED: &str = "UPDATE pulls SET base_branch = ?3
131 WHERE repo_id = ?1 AND base_branch = ?2 AND status IN ('draft', 'open')";
132
Inbox: the events service tells people what needs them as events arrive133impl Work {
134 /// Whether work may start on the repository: neither archived nor
135 /// deleted. When repos cannot say, it is taken as active, and the
136 /// repos calls that follow (which hide deleted repositories) decide.
137 pub(crate) async fn repo_active(&self, repo_id: &str) -> Result<bool> {
138 let status: Result<RepoStatus> = g1t_kit::call(
139 &self.repos,
140 "status_by_id",
141 &StatusByIdArgs {
142 id: repo_id.to_owned(),
143 },
144 )
145 .await;
146 Ok(match status {
147 Ok(status) => status.active(),
148 Err(error) => {
149 worker::console_error!("status_by_id {repo_id}: {error}");
150 true
151 }
152 })
153 }
154
155 /// See [`RunsInRepoArgs`].
156 pub(crate) async fn runs_in_repo(&self, a: RunsInRepoArgs) -> Result<Vec<RepoRun>> {
157 let hour_ago = rfc3339(now_ms().saturating_sub(60 * 60 * 1000));
158 self.db
159 .prepare(
160 "SELECT id AS run_id, sandbox, kind, status, pull_id FROM agent_runs
161 WHERE repo_id = ?1 AND (status IN ('queued', 'running')
162 OR (status = 'stopped' AND step = ?2 AND finished_at >= ?3))
163 ORDER BY created_at LIMIT 200",
164 )
165 .bind(&[a.repo_id.as_str().into(), STOPPED_STEP.into(), hour_ago.as_str().into()])?
166 .all()
167 .await?
168 .results::<RepoRun>()
169 }
170
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.171 /// `repo.deleted` and `repo.archived`: its runs stop (the runner stops
172 /// their sandboxes, see [`RunsInRepoArgs`]). Rows are kept, for a restore.
173 async fn stop_runs_in(&self, repo_id: &str) -> Result<()> {
Inbox: the events service tells people what needs them as events arrive174 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.175 self.db
Inbox: the events service tells people what needs them as events arrive176 .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.177 .bind(&[STOPPED_STEP.into(), now.as_str().into(), repo_id.into()])?
178 .run()
179 .await?;
Inbox: the events service tells people what needs them as events arrive180 Ok(())
181 }
182
183 /// `repo.purged`, `repo.deleted`, `repo.archived` and `branch.renamed`.
184 /// Says whether `event` was one this handles completely.
185 pub(crate) async fn on_retired(&self, event: &Event) -> Result<bool> {
186 if g1t_kit::lifecycle::on_purged(&self.db, event, PURGED).await? {
187 return Ok(true);
188 }
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.189 if let Some((repo_id, _, _)) = stopping(event) {
190 self.stop_runs_in(&repo_id).await?;
Inbox: the events service tells people what needs them as events arrive191 return Ok(true);
192 }
193 if event.kind != "branch.renamed" {
194 return Ok(false);
195 }
196 let Ok(renamed) = serde_json::from_value::<BranchRenamed>(event.data.clone()) else {
197 worker::console_error!("branch.renamed {} could not be read", event.id);
198 return Ok(true);
199 };
200 if renamed.from == renamed.to {
201 return Ok(true);
202 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar203 let names = [
204 renamed.repo_id.as_str().into(),
205 renamed.from.as_str().into(),
206 renamed.to.as_str().into(),
207 ];
Inbox: the events service tells people what needs them as events arrive208 self.db
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar209 .batch(vec![
210 self.db.prepare(BRANCH_RENAMED).bind(&names)?,
211 self.db.prepare(BASE_RENAMED).bind(&names)?,
212 ])
Inbox: the events service tells people what needs them as events arrive213 .await?;
214 Ok(true)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 fn repo(archived_at: Option<&str>) -> Repo {
223 Repo {
224 id: "rep_1".into(),
225 namespace: "acme".into(),
226 name: "web".into(),
227 description: None,
228 is_private: false,
229 owner_id: "usr_1".into(),
230 default_branch: "main".into(),
231 fork_of: None,
232 protected: false,
233 created_at: "2026-10-05T00:00:00Z".into(),
234 topics: Vec::new(),
235 website: None,
236 archived_at: archived_at.map(str::to_owned),
237 }
238 }
239
240 #[test]
241 fn an_archived_repository_refuses_changes() {
242 assert!(matches!(writable(&repo(None)), Outcome::Ok(())));
243 match writable(&repo(Some("2026-10-05T00:00:00Z"))) {
244 Outcome::Fail(failure) => {
245 assert_eq!(failure.code, FailureCode::Forbidden);
246 assert!(failure.message.contains("acme/web is archived"));
247 }
248 Outcome::Ok(()) => panic!("an archived repository was writable"),
249 }
250 }
251
252 #[test]
253 fn purging_takes_only_the_repository_id() {
254 for sql in PURGED {
255 assert_eq!(g1t_kit::transfer::parameters(sql), 1, "{sql}");
256 }
257 // Rows kept by pull request go before the pull requests do.
258 let pulls = PURGED.iter().position(|sql| sql.starts_with("DELETE FROM pulls")).unwrap();
259 for table in ["session_entries", "check_runs", "review_runs", "agent_messages"] {
260 let at = PURGED.iter().position(|sql| sql.contains(&format!("FROM {table} "))).unwrap();
261 assert!(at < pulls, "{table}");
262 }
263 }
264
265 fn event(kind: &str, data: serde_json::Value) -> Event {
266 Event {
267 id: "evt_1".into(),
268 kind: kind.into(),
269 source: "repos".into(),
270 time: "2026-10-05T00:00:00Z".into(),
271 repo_id: Some("rep_1".into()),
272 actor: None,
273 data,
274 }
275 }
276
277 #[test]
278 fn deleting_or_archiving_stops_runs_and_unarchiving_does_not() {
279 let named = Some(("rep_1".to_owned(), "acme".to_owned(), "web".to_owned()));
280 let data = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": true });
281 assert_eq!(stopping(&event("repo.deleted", data.clone())), named);
282 assert_eq!(stopping(&event("repo.archived", data)), named);
283 let unarchived = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": false });
284 assert_eq!(stopping(&event("repo.unarchived", unarchived.clone())), None);
285 assert_eq!(stopping(&event("repo.archived", unarchived)), None);
286 assert_eq!(STOP_RUNS.matches('?').count(), 4);
287 }
288
289 #[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.290 fn renamed_branches_bind_three_names() {
Inbox: the events service tells people what needs them as events arrive291 assert_eq!(g1t_kit::transfer::parameters(BRANCH_RENAMED), 3);
292 }
293}

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