Skip to content
354 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.

Fast pages, required checks on the branch, self-hosted runners, honest incidents1//! Statuses on commits: what workflow runs (and other tools, such as
2//! deployments) say about a pull request's head. These are its checks.
3//!
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge4//! The rules of the branch a pull request merges into name the checks that
5//! must pass (rulesets.rs; `RepoSettings::required_checks` as they stack):
6//! a required check that failed, is still running or has not reported
7//! refuses the merge, for everyone and for the merge queue (`g1t_rules`
8//! says so). Where g1t sees an agent's pull request through,
Fast pages, required checks on the branch, self-hosted runners, honest incidents9//! any check that failed sends the agent back to fix it, with what the
10//! failing jobs printed; once it is out of revisions, only a required
11//! check holds the pull request for a person.
The keeper reads Cloudflare as it really answers12
A mirror of a GitHub repository whose workflows g1t runs, in CI failover, during a takeover or with CI kept warm, now shows their results on GitHub: each status g1t sets on a commit, a workflow run's or a deploy's, goes to the commit there through g1t's GitHub App as a commit status named under g1t / (g1t / CI / push; g1t / deploy stays as it is), with its state, its description and a link back to g1t, once each time it changes, and commits made on g1t during a takeover are skipped. It is on by default and turned off with Report checks to GitHub in the mirror's settings, or report_checks through the API. The app needs the Commit statuses permission; until an installation has it, the mirror's settings say g1t can't report checks to GitHub and why, and the line goes once a status gets through. Statuses set by workflow runs, deploys and g1t's other parts are now published as status.created, as ones set through the API were, so webhooks hear them too. The mirroring, actions, GitHub, checks, webhooks and self-hosting guides say so.13use g1t_contracts::checks::StatusEvent;
The keeper reads Cloudflare as it really answers14use g1t_contracts::events::ChecksEvent;
15use g1t_contracts::time::rfc3339;
Fast pages, required checks on the branch, self-hosted runners, honest incidents16use g1t_contracts::work::{
17 CommitStatus, RequiredCheck, RequiredState, SeenCheck, SeenChecksArgs, SetCommitStatusArgs, check_name,
18 required_checks,
19};
The keeper reads Cloudflare as it really answers20use g1t_contracts::{FailureCode, Outcome};
21use g1t_kit::now_ms;
22use serde::Deserialize;
23use worker::Result;
24
25use crate::Work;
26
27#[derive(Deserialize)]
28struct StatusRow {
29 context: String,
30 state: String,
31 description: Option<String>,
32 target_url: Option<String>,
33 updated_at: String,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge34 #[serde(default)]
35 source: Option<String>,
Merge checks: statuses and check runs on every commit36 #[serde(default)]
37 check_run_id: Option<String>,
The keeper reads Cloudflare as it really answers38}
39
40impl From<StatusRow> for CommitStatus {
41 fn from(row: StatusRow) -> Self {
42 CommitStatus {
43 context: row.context,
44 state: row.state,
45 description: row.description,
46 target_url: row.target_url,
47 updated_at: row.updated_at,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge48 source: row.source,
Merge checks: statuses and check runs on every commit49 check_run_id: row.check_run_id,
The keeper reads Cloudflare as it really answers50 }
51 }
52}
53
54#[derive(Deserialize)]
55struct HeadRow {
56 id: String,
57 number: u32,
58}
59
Fast pages, required checks on the branch, self-hosted runners, honest incidents60/// What a commit's checks say: every status still running and every one
61/// that failed, by context, and where each required check stands.
62#[derive(Clone, Debug, Default, PartialEq)]
The keeper reads Cloudflare as it really answers63pub(crate) struct WorkflowFacts {
64 pub(crate) pending: Vec<String>,
65 pub(crate) failed: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents66 pub(crate) required: Vec<RequiredCheck>,
The keeper reads Cloudflare as it really answers67}
68
69impl WorkflowFacts {
Fast pages, required checks on the branch, self-hosted runners, honest incidents70 /// `required` names the checks the default branch's protection requires.
71 pub(crate) fn of(statuses: &[CommitStatus], required: &[String]) -> WorkflowFacts {
The keeper reads Cloudflare as it really answers72 WorkflowFacts {
73 pending: statuses.iter().filter(|s| s.state == "pending").map(|s| s.context.clone()).collect(),
74 failed: statuses.iter().filter(|s| s.state == "failure" || s.state == "error").map(|s| s.context.clone()).collect(),
Fast pages, required checks on the branch, self-hosted runners, honest incidents75 required: required_checks(required, statuses),
The keeper reads Cloudflare as it really answers76 }
77 }
78
Fast pages, required checks on the branch, self-hosted runners, honest incidents79 fn required_in(&self, state: RequiredState) -> Vec<String> {
80 self.required.iter().filter(|check| check.state == state).map(|check| check.name.clone()).collect()
81 }
82
83 /// The required checks that failed, by name.
84 pub(crate) fn required_failed(&self) -> Vec<String> {
85 self.required_in(RequiredState::Failure)
86 }
87
88 /// The required checks nothing has reported on the commit yet.
89 pub(crate) fn expected(&self) -> Vec<String> {
90 self.required_in(RequiredState::Expected)
91 }
92}
93
94/// The check names in `(context, last reported)` rows, most recent first:
95/// each name once, with the events it was reported for.
96pub(crate) fn seen(rows: Vec<(String, String)>) -> Vec<SeenCheck> {
97 let mut rows = rows;
98 rows.sort_by(|a, b| b.1.cmp(&a.1));
99 let mut out: Vec<SeenCheck> = Vec::new();
100 for (context, at) in rows {
101 let (name, event) = check_name(&context);
102 match out.iter_mut().find(|seen| seen.name.eq_ignore_ascii_case(name)) {
103 Some(seen) => {
104 if let Some(event) = event
105 && !seen.events.iter().any(|known| known == event)
106 {
107 seen.events.push(event.to_owned());
108 }
109 }
110 None => out.push(SeenCheck {
111 name: name.to_owned(),
112 events: event.map(|event| vec![event.to_owned()]).unwrap_or_default(),
113 last_seen: at,
114 }),
115 }
116 }
117 out
118}
119
120/// How far back `seen_checks` looks, and the most contexts it reads.
121const SEEN_DAYS: u64 = 30;
122const SEEN_LIMIT: u32 = 200;
123
124#[derive(Deserialize)]
125struct SeenRow {
126 context: String,
127 at: String,
128}
129
The keeper reads Cloudflare as it really answers130pub(crate) fn list(names: &[String]) -> String {
131 match names {
132 [] => String::new(),
133 [one] => one.clone(),
134 [rest @ .., last] => format!("{} and {last}", rest.join(", ")),
135 }
136}
137
138impl Work {
Fast pages, required checks on the branch, self-hosted runners, honest incidents139 /// Where a commit's checks stand, against the repository's required ones.
140 pub(crate) async fn facts(&self, repo_id: &str, sha: Option<&str>) -> Result<WorkflowFacts> {
141 let (statuses, settings) =
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge142 futures_util::future::try_join(self.statuses(repo_id, sha), self.settings_by_id(repo_id)).await?;
Fast pages, required checks on the branch, self-hosted runners, honest incidents143 Ok(WorkflowFacts::of(&statuses, &settings.required_checks))
144 }
145
146 /// The check names reported on a repository's commits lately, for
147 /// choosing which to require.
148 pub(crate) async fn seen_checks(&self, a: SeenChecksArgs) -> Result<Outcome<Vec<SeenCheck>>> {
149 let repo = match self.repo(&a.repo, &a.viewer).await? {
150 Outcome::Ok(repo) => repo,
151 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
152 };
153 let since = rfc3339(now_ms().saturating_sub(SEEN_DAYS * 24 * 60 * 60 * 1000));
154 let rows = self
155 .db
156 .prepare(
157 "SELECT context, MAX(updated_at) AS at FROM commit_statuses
158 WHERE repo_id = ? AND updated_at >= ? GROUP BY context ORDER BY at DESC LIMIT ?",
159 )
160 .bind(&[repo.id.as_str().into(), since.into(), SEEN_LIMIT.into()])?
161 .all()
162 .await?
163 .results::<SeenRow>()?;
164 Ok(Outcome::Ok(seen(rows.into_iter().map(|row| (row.context, row.at)).collect())))
165 }
166
The keeper reads Cloudflare as it really answers167 pub(crate) async fn statuses(&self, repo_id: &str, sha: Option<&str>) -> Result<Vec<CommitStatus>> {
168 let Some(sha) = sha else { return Ok(Vec::new()) };
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily169 if let Some(found) = self.prefetched_repo(repo_id).filter(|found| found.head.as_deref() == Some(sha)) {
170 return Ok(found
171 .rows::<StatusRow>(crate::prefetch::Slot::Statuses)?
172 .into_iter()
173 .map(CommitStatus::from)
174 .collect());
175 }
The keeper reads Cloudflare as it really answers176 Ok(self
177 .db
Merge checks: statuses and check runs on every commit178 .prepare("SELECT context, state, description, target_url, updated_at, source, check_run_id FROM commit_statuses WHERE repo_id = ? AND sha = ? ORDER BY context")
The keeper reads Cloudflare as it really answers179 .bind(&[repo_id.into(), sha.into()])?
180 .all()
181 .await?
182 .results::<StatusRow>()?
183 .into_iter()
184 .map(CommitStatus::from)
185 .collect())
186 }
187
A mirror of a GitHub repository whose workflows g1t runs, in CI failover, during a takeover or with CI kept warm, now shows their results on GitHub: each status g1t sets on a commit, a workflow run's or a deploy's, goes to the commit there through g1t's GitHub App as a commit status named under g1t / (g1t / CI / push; g1t / deploy stays as it is), with its state, its description and a link back to g1t, once each time it changes, and commits made on g1t during a takeover are skipped. It is on by default and turned off with Report checks to GitHub in the mirror's settings, or report_checks through the API. The app needs the Commit statuses permission; until an installation has it, the mirror's settings say g1t can't report checks to GitHub and why, and the line goes once a status gets through. Statuses set by workflow runs, deploys and g1t's other parts are now published as status.created, as ones set through the API were, so webhooks hear them too. The mirroring, actions, GitHub, checks, webhooks and self-hosting guides say so.188 /// A status from another part of g1t: a workflow run, a deploy, a scan.
189 /// Published as `status.created`, as one set through the API is, so
190 /// webhooks hear it and a GitHub mirror's commit is told (integrations).
191 /// Publishing is best effort: the status is kept either way.
The keeper reads Cloudflare as it really answers192 pub(crate) async fn set_commit_status(&self, a: SetCommitStatusArgs) -> Result<Outcome<bool>> {
A mirror of a GitHub repository whose workflows g1t runs, in CI failover, during a takeover or with CI kept warm, now shows their results on GitHub: each status g1t sets on a commit, a workflow run's or a deploy's, goes to the commit there through g1t's GitHub App as a commit status named under g1t / (g1t / CI / push; g1t / deploy stays as it is), with its state, its description and a link back to g1t, once each time it changes, and commits made on g1t during a takeover are skipped. It is on by default and turned off with Report checks to GitHub in the mirror's settings, or report_checks through the API. The app needs the Commit statuses permission; until an installation has it, the mirror's settings say g1t can't report checks to GitHub and why, and the line goes once a status gets through. Statuses set by workflow runs, deploys and g1t's other parts are now published as status.created, as ones set through the API were, so webhooks hear them too. The mirroring, actions, GitHub, checks, webhooks and self-hosting guides say so.193 let event = StatusEvent {
194 repo_id: a.repo_id.clone(),
195 sha: a.sha.clone(),
196 context: a.context.clone(),
197 state: a.state.clone(),
198 description: a.description.clone(),
199 target_url: a.target_url.clone(),
200 };
201 let repo_id = a.repo_id.clone();
202 let stored = self.store_status(a, None).await?;
203 if matches!(stored, Outcome::Ok(_))
204 && let Err(error) = self.publish_as("status.created", &repo_id, None, event).await
205 {
206 worker::console_error!("status.created not published: {error}");
207 }
208 Ok(stored)
Merge checks: statuses and check runs on every commit209 }
210
211 /// Sets a status, standing for check run `check_run_id` if it does,
212 /// and moves on what was waiting for the commit's checks.
213 pub(crate) async fn store_status(&self, a: SetCommitStatusArgs, check_run_id: Option<&str>) -> Result<Outcome<bool>> {
The keeper reads Cloudflare as it really answers214 if !matches!(a.state.as_str(), "pending" | "success" | "failure" | "error") {
215 return Ok(Outcome::fail(FailureCode::Invalid, "`state` is pending, success, failure or error."));
216 }
217 self.db
218 .prepare(
Merge checks: statuses and check runs on every commit219 "INSERT INTO commit_statuses (repo_id, sha, context, state, description, target_url, updated_at, source, check_run_id)
220 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
The keeper reads Cloudflare as it really answers221 ON CONFLICT (repo_id, sha, context) DO UPDATE SET
222 state = excluded.state, description = excluded.description,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge223 target_url = excluded.target_url, updated_at = excluded.updated_at,
Merge checks: statuses and check runs on every commit224 source = excluded.source, check_run_id = excluded.check_run_id",
The keeper reads Cloudflare as it really answers225 )
226 .bind(&[
227 a.repo_id.as_str().into(),
228 a.sha.as_str().into(),
229 a.context.as_str().into(),
230 a.state.as_str().into(),
231 a.description.as_deref().map_or(worker::wasm_bindgen::JsValue::NULL, Into::into),
232 a.target_url.as_deref().map_or(worker::wasm_bindgen::JsValue::NULL, Into::into),
233 rfc3339(now_ms()).into(),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge234 a.source.as_deref().map_or(worker::wasm_bindgen::JsValue::NULL, Into::into),
Merge checks: statuses and check runs on every commit235 check_run_id.map_or(worker::wasm_bindgen::JsValue::NULL, Into::into),
The keeper reads Cloudflare as it really answers236 ])?
237 .run()
238 .await?;
239 if a.state == "pending" {
240 return Ok(Outcome::Ok(true));
241 }
242 // Once every workflow on a pull request's head has finished, its
243 // lifecycle moves on, as it does when its checks finish.
Fast pages, required checks on the branch, self-hosted runners, honest incidents244 let facts = self.facts(&a.repo_id, Some(&a.sha)).await?;
The keeper reads Cloudflare as it really answers245 if !facts.pending.is_empty() {
246 return Ok(Outcome::Ok(true));
247 }
248 // A merge queue state waiting on its merge_group workflows.
Fast pages, required checks on the branch, self-hosted runners, honest incidents249 self.merge_group_finished(&a.repo_id, &a.sha, &facts).await?;
The keeper reads Cloudflare as it really answers250 let heads = self
251 .db
252 .prepare("SELECT id, number FROM pulls WHERE repo_id = ? AND head_commit = ? AND status IN ('draft', 'open')")
253 .bind(&[a.repo_id.as_str().into(), a.sha.as_str().into()])?
254 .all()
255 .await?
256 .results::<HeadRow>()?;
257 for head in heads {
258 // A pull request g1t stopped on picks back up once what stopped
259 // it passes: its workflows, and its checks if it has any. The
260 // lifecycle then decides again, within its usual limits.
261 if facts.failed.is_empty() {
262 let resumed = self
263 .db
264 .prepare(
265 "UPDATE pulls SET stalled = NULL WHERE id = ? AND managed = 1 AND stalled IS NOT NULL
266 AND (check_status IS NULL OR check_status = 'passed') RETURNING id AS value",
267 )
268 .bind(&[head.id.as_str().into()])?
269 .first::<crate::rows::ValueRow>(None)
270 .await?;
271 if resumed.is_some() {
Events: review requests, assignments, stops and deployments are published272 self.announce_resumed(&head.id, None).await?;
The keeper reads Cloudflare as it really answers273 self.note(
274 &a.repo_id,
275 head.number,
276 (crate::lifecycle::POLICY_ACTOR_ID, crate::lifecycle::POLICY_ACTOR_NAME),
277 "picked this back up: its workflows pass now",
278 )
279 .await?;
280 }
281 }
282 self.publish_as(
283 "checks.completed",
284 &a.repo_id,
285 None,
286 ChecksEvent {
287 pull_id: head.id,
288 repo_id: a.repo_id.clone(),
289 number: head.number,
290 status: if facts.failed.is_empty() { "passed" } else { "failed" },
291 commit: a.sha.clone(),
292 },
293 )
294 .await?;
295 }
296 Ok(Outcome::Ok(true))
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 fn status(context: &str, state: &str) -> CommitStatus {
305 CommitStatus {
306 context: context.into(),
307 state: state.into(),
308 description: None,
309 target_url: None,
310 updated_at: String::new(),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge311 source: None,
Merge checks: statuses and check runs on every commit312 check_run_id: None,
The keeper reads Cloudflare as it really answers313 }
314 }
315
Fast pages, required checks on the branch, self-hosted runners, honest incidents316 fn names(list: &[&str]) -> Vec<String> {
317 list.iter().map(|name| (*name).to_owned()).collect()
318 }
319
320 #[test]
321 fn only_required_checks_hold_a_merge() {
322 let statuses = [status("CI / push", "pending"), status("Lint / pull_request", "failure"), status("Docs", "success")];
323 // Nothing required: nothing holds it, whatever failed.
324 let free = WorkflowFacts::of(&statuses, &[]);
325 assert_eq!(free.pending, ["CI / push"]);
326 assert_eq!(free.failed, ["Lint / pull_request"]);
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge327 assert!(free.required_failed().is_empty() && free.expected().is_empty());
Fast pages, required checks on the branch, self-hosted runners, honest incidents328 let both = WorkflowFacts::of(&statuses, &names(&["CI", "Lint"]));
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge329 assert_eq!(both.required_failed(), ["Lint"]);
330 assert!(WorkflowFacts::of(&[status("Docs", "success")], &names(&["Docs"])).required_failed().is_empty());
Fast pages, required checks on the branch, self-hosted runners, honest incidents331 }
332
333 #[test]
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge334 fn a_required_check_nothing_reported_is_expected() {
Fast pages, required checks on the branch, self-hosted runners, honest incidents335 let facts = WorkflowFacts::of(&[status("CI / pull_request", "success")], &names(&["CI", "Deploy"]));
336 assert_eq!(facts.expected(), ["Deploy"]);
337 }
338
The keeper reads Cloudflare as it really answers339 #[test]
Fast pages, required checks on the branch, self-hosted runners, honest incidents340 fn seen_checks_are_named_once_with_their_events() {
341 let rows = vec![
342 ("CI / push".to_owned(), "2026-10-01T00:00:00Z".to_owned()),
343 ("CI / pull_request".to_owned(), "2026-10-03T00:00:00Z".to_owned()),
344 ("g1t / deploy".to_owned(), "2026-10-02T00:00:00Z".to_owned()),
345 ];
346 let seen = seen(rows);
347 assert_eq!(seen.len(), 2);
348 assert_eq!(seen[0].name, "CI");
349 assert_eq!(seen[0].events, ["pull_request", "push"]);
350 assert_eq!(seen[0].last_seen, "2026-10-03T00:00:00Z");
351 assert_eq!(seen[1].name, "g1t / deploy");
352 assert!(seen[1].events.is_empty());
The keeper reads Cloudflare as it really answers353 }
354}

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