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

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

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