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

Fixes from the page sweep1//! 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.
Fixes from the page sweep3//!
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
Fixes from the page sweep6//! 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};
Fixes from the page sweep11use 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.
Fixes from the page sweep21pub(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(()),
Fixes from the page sweep25 }
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)",
44 "DELETE FROM check_runs WHERE pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
45 "DELETE FROM review_runs WHERE pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
46 "DELETE FROM agent_messages WHERE repo_id = ?1 OR pull_id IN (SELECT id FROM pulls WHERE repo_id = ?1)",
47 "DELETE FROM queue_entries WHERE repo_id = ?1",
Merge branch 'worktree-agent-ab8033ab973f8d99a'48 "DELETE FROM queue_probes WHERE repo_id = ?1",
49 "DELETE FROM decisions WHERE repo_id = ?1",
Fixes from the page sweep50 "DELETE FROM agent_mentions WHERE repo_id = ?1",
51 "DELETE FROM agent_rules WHERE repo_id = ?1",
52 "DELETE FROM pull_confidence WHERE repo_id = ?1",
53 "DELETE FROM run_confidence WHERE repo_id = ?1",
54 "DELETE FROM confidence_rules WHERE repo_id = ?1",
55 "DELETE FROM agent_runs WHERE repo_id = ?1",
56 "DELETE FROM commit_statuses WHERE repo_id = ?1",
Merge checks: statuses and check runs on every commit57 "DELETE FROM commit_check_annotations WHERE run_id IN (SELECT id FROM commit_check_runs WHERE repo_id = ?1)",
58 "DELETE FROM commit_check_runs WHERE repo_id = ?1",
59 "DELETE FROM commit_check_suites WHERE repo_id = ?1",
Fixes from the page sweep60 "DELETE FROM plans WHERE repo_id = ?1",
61 "DELETE FROM comments WHERE repo_id = ?1",
62 "DELETE FROM pulls WHERE repo_id = ?1",
63 "DELETE FROM issues WHERE repo_id = ?1",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar64 "DELETE FROM labels WHERE repo_id = ?1",
65 "DELETE FROM milestones WHERE repo_id = ?1",
Fixes from the page sweep66 "DELETE FROM counters WHERE repo_id = ?1",
67 "DELETE FROM repo_settings WHERE repo_id = ?1",
68 "DELETE FROM memories WHERE scope = 'project' AND scope_key = ?1",
69 "DELETE FROM guardrails WHERE scope <> 'workspace' AND scope_key = ?1",
70];
71
72/// What a run stopped because its repository was archived or deleted says
73/// as its last step; [`Work::runs_in_repo`] finds such runs by it.
74const STOPPED_STEP: &str = "Stopped: the repository was archived or deleted.";
75
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.76/// `repo.deleted` and `repo.archived`: the repository's runs that
Fixes from the page sweep77/// are queued or running stop, `?1` the step they end on, `?2` the time,
78/// `?3` its id.
79const STOP_RUNS: &str = "UPDATE agent_runs SET status = 'stopped', step = ?1, finished_at = ?2, updated_at = ?2
80 WHERE repo_id = ?3 AND status IN ('queued', 'running')";
81
82/// `runs_in_repo` (internal, for the runner): the agent runs of a
83/// repository whose sandboxes should be stopped: those queued or running,
84/// and those work stopped in the last hour because the repository was
85/// archived or deleted. Takes `{ "repoId" }`; returns `[RepoRun]`.
86#[derive(Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub(crate) struct RunsInRepoArgs {
89 pub repo_id: String,
90}
91
92#[derive(Debug, Deserialize, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub(crate) struct RepoRun {
95 pub run_id: String,
96 pub sandbox: String,
97 pub kind: String,
98 pub status: String,
99 pub pull_id: Option<String>,
100}
101
102/// The repository, by id and path, that `event` says was deleted or
103/// archived (not unarchived): its runs stop.
104fn stopping(event: &Event) -> Option<(String, String, String)> {
105 let data = event.data.clone();
106 match event.kind.as_str() {
107 "repo.deleted" => serde_json::from_value::<RepoDeleted>(data).ok().map(|e| (e.repo_id, e.namespace, e.name)),
108 "repo.archived" => serde_json::from_value::<RepoArchived>(data)
109 .ok()
110 .filter(|e| e.archived)
111 .map(|e| (e.repo_id, e.namespace, e.name)),
112 _ => None,
113 }
114}
115
116/// `branch.renamed`: open pull requests from `?2` in repository `?1`
117/// come from `?3` now. A fork carries its change on its own default
118/// branch, so only branches of the repository itself are named.
119const BRANCH_RENAMED: &str = "UPDATE pulls SET source_branch = ?3
120 WHERE repo_id = ?1 AND source_branch = ?2 AND fork_repo_id IS NULL
121 AND status IN ('draft', 'open')";
122
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar123/// `branch.renamed`: open pull requests into `?2` merge into `?3` now.
124/// Those into the default branch name none, and follow it as they are.
125const BASE_RENAMED: &str = "UPDATE pulls SET base_branch = ?3
126 WHERE repo_id = ?1 AND base_branch = ?2 AND status IN ('draft', 'open')";
127
Fixes from the page sweep128impl Work {
129 /// Whether work may start on the repository: neither archived nor
130 /// deleted. When repos cannot say, it is taken as active, and the
131 /// repos calls that follow (which hide deleted repositories) decide.
132 pub(crate) async fn repo_active(&self, repo_id: &str) -> Result<bool> {
133 let status: Result<RepoStatus> = g1t_kit::call(
134 &self.repos,
135 "status_by_id",
136 &StatusByIdArgs {
137 id: repo_id.to_owned(),
138 },
139 )
140 .await;
141 Ok(match status {
142 Ok(status) => status.active(),
143 Err(error) => {
144 worker::console_error!("status_by_id {repo_id}: {error}");
145 true
146 }
147 })
148 }
149
150 /// See [`RunsInRepoArgs`].
151 pub(crate) async fn runs_in_repo(&self, a: RunsInRepoArgs) -> Result<Vec<RepoRun>> {
152 let hour_ago = rfc3339(now_ms().saturating_sub(60 * 60 * 1000));
153 self.db
154 .prepare(
155 "SELECT id AS run_id, sandbox, kind, status, pull_id FROM agent_runs
156 WHERE repo_id = ?1 AND (status IN ('queued', 'running')
157 OR (status = 'stopped' AND step = ?2 AND finished_at >= ?3))
158 ORDER BY created_at LIMIT 200",
159 )
160 .bind(&[a.repo_id.as_str().into(), STOPPED_STEP.into(), hour_ago.as_str().into()])?
161 .all()
162 .await?
163 .results::<RepoRun>()
164 }
165
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.166 /// `repo.deleted` and `repo.archived`: its runs stop (the runner stops
167 /// their sandboxes, see [`RunsInRepoArgs`]). Rows are kept, for a restore.
168 async fn stop_runs_in(&self, repo_id: &str) -> Result<()> {
Fixes from the page sweep169 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.170 self.db
Fixes from the page sweep171 .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.172 .bind(&[STOPPED_STEP.into(), now.as_str().into(), repo_id.into()])?
173 .run()
174 .await?;
Fixes from the page sweep175 Ok(())
176 }
177
178 /// `repo.purged`, `repo.deleted`, `repo.archived` and `branch.renamed`.
179 /// Says whether `event` was one this handles completely.
180 pub(crate) async fn on_retired(&self, event: &Event) -> Result<bool> {
181 if g1t_kit::lifecycle::on_purged(&self.db, event, PURGED).await? {
182 return Ok(true);
183 }
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.184 if let Some((repo_id, _, _)) = stopping(event) {
185 self.stop_runs_in(&repo_id).await?;
Fixes from the page sweep186 return Ok(true);
187 }
188 if event.kind != "branch.renamed" {
189 return Ok(false);
190 }
191 let Ok(renamed) = serde_json::from_value::<BranchRenamed>(event.data.clone()) else {
192 worker::console_error!("branch.renamed {} could not be read", event.id);
193 return Ok(true);
194 };
195 if renamed.from == renamed.to {
196 return Ok(true);
197 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar198 let names = [
199 renamed.repo_id.as_str().into(),
200 renamed.from.as_str().into(),
201 renamed.to.as_str().into(),
202 ];
Fixes from the page sweep203 self.db
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar204 .batch(vec![
205 self.db.prepare(BRANCH_RENAMED).bind(&names)?,
206 self.db.prepare(BASE_RENAMED).bind(&names)?,
207 ])
Fixes from the page sweep208 .await?;
209 Ok(true)
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 fn repo(archived_at: Option<&str>) -> Repo {
218 Repo {
219 id: "rep_1".into(),
220 namespace: "acme".into(),
221 name: "web".into(),
222 description: None,
223 is_private: false,
224 owner_id: "usr_1".into(),
225 default_branch: "main".into(),
226 fork_of: None,
227 protected: false,
228 created_at: "2026-10-05T00:00:00Z".into(),
229 topics: Vec::new(),
230 website: None,
231 archived_at: archived_at.map(str::to_owned),
232 }
233 }
234
235 #[test]
236 fn an_archived_repository_refuses_changes() {
237 assert!(matches!(writable(&repo(None)), Outcome::Ok(())));
238 match writable(&repo(Some("2026-10-05T00:00:00Z"))) {
239 Outcome::Fail(failure) => {
240 assert_eq!(failure.code, FailureCode::Forbidden);
241 assert!(failure.message.contains("acme/web is archived"));
242 }
243 Outcome::Ok(()) => panic!("an archived repository was writable"),
244 }
245 }
246
247 #[test]
248 fn purging_takes_only_the_repository_id() {
249 for sql in PURGED {
250 assert_eq!(g1t_kit::transfer::parameters(sql), 1, "{sql}");
251 }
252 // Rows kept by pull request go before the pull requests do.
253 let pulls = PURGED.iter().position(|sql| sql.starts_with("DELETE FROM pulls")).unwrap();
254 for table in ["session_entries", "check_runs", "review_runs", "agent_messages"] {
255 let at = PURGED.iter().position(|sql| sql.contains(&format!("FROM {table} "))).unwrap();
256 assert!(at < pulls, "{table}");
257 }
258 }
259
260 fn event(kind: &str, data: serde_json::Value) -> Event {
261 Event {
262 id: "evt_1".into(),
263 kind: kind.into(),
264 source: "repos".into(),
265 time: "2026-10-05T00:00:00Z".into(),
266 repo_id: Some("rep_1".into()),
267 actor: None,
268 data,
269 }
270 }
271
272 #[test]
273 fn deleting_or_archiving_stops_runs_and_unarchiving_does_not() {
274 let named = Some(("rep_1".to_owned(), "acme".to_owned(), "web".to_owned()));
275 let data = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": true });
276 assert_eq!(stopping(&event("repo.deleted", data.clone())), named);
277 assert_eq!(stopping(&event("repo.archived", data)), named);
278 let unarchived = serde_json::json!({ "repoId": "rep_1", "namespace": "acme", "name": "web", "archived": false });
279 assert_eq!(stopping(&event("repo.unarchived", unarchived.clone())), None);
280 assert_eq!(stopping(&event("repo.archived", unarchived)), None);
281 assert_eq!(STOP_RUNS.matches('?').count(), 4);
282 }
283
284 #[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.285 fn renamed_branches_bind_three_names() {
Fixes from the page sweep286 assert_eq!(g1t_kit::transfer::parameters(BRANCH_RENAMED), 3);
287 }
288}

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