Skip to content

Commit

Merge checks: statuses and check runs on every commit

Conflict: Op::ALL is 257 (the gateway's integration update and the twelve checks operations). The docs' OpenAPI copy is regenerated.

syntaqxcommitted Parentsa96bdc7601a45fBrowse files
66 files+1256−120/66 viewed
+413−0
1+//! Checks over REST and MCP: statuses on commits, and check runs and
2+//! check suites, in GitHub's shapes so that existing integrations and
3+//! actions report to g1t unchanged.
4+//!
5+//! The work service keeps them and decides who may read and report them
6+//! (`g1t_contracts::checks`). A g1t Actions job is a check run here too,
7+//! and its workflow run the suite.
8+
9+use g1t_contracts::checks::*;
10+use g1t_contracts::{FailureCode, Outcome, Viewer};
11+use serde::Serialize;
12+use serde::de::DeserializeOwned;
13+use serde_json::{Map, Value, json};
14+use worker::Result;
15+
16+use crate::operations::Services;
17+
18+/// One operation on checks.
19+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20+pub enum ChecksOp {
21+ CreateCommitStatus,
22+ ListCommitStatuses,
23+ GetCombinedStatus,
24+ CreateCheckRun,
25+ UpdateCheckRun,
26+ GetCheckRun,
27+ ListCheckRunAnnotations,
28+ RerequestCheckRun,
29+ ListCheckRunsForRef,
30+ ListCheckSuitesForRef,
31+ GetCheckSuite,
32+ RerequestCheckSuite,
33+}
34+
35+impl ChecksOp {
36+ /// Every one: `Op::ALL` lists each as `Op::Checks(…)`, which a test
37+ /// checks against this.
38+ #[cfg(test)]
39+ pub const ALL: [ChecksOp; 12] = [
40+ ChecksOp::CreateCommitStatus,
41+ ChecksOp::ListCommitStatuses,
42+ ChecksOp::GetCombinedStatus,
43+ ChecksOp::CreateCheckRun,
44+ ChecksOp::UpdateCheckRun,
45+ ChecksOp::GetCheckRun,
46+ ChecksOp::ListCheckRunAnnotations,
47+ ChecksOp::RerequestCheckRun,
48+ ChecksOp::ListCheckRunsForRef,
49+ ChecksOp::ListCheckSuitesForRef,
50+ ChecksOp::GetCheckSuite,
51+ ChecksOp::RerequestCheckSuite,
52+ ];
53+
54+ pub fn name(self) -> &'static str {
55+ match self {
56+ ChecksOp::CreateCommitStatus => "create_commit_status",
57+ ChecksOp::ListCommitStatuses => "list_commit_statuses",
58+ ChecksOp::GetCombinedStatus => "get_combined_status",
59+ ChecksOp::CreateCheckRun => "create_check_run",
60+ ChecksOp::UpdateCheckRun => "update_check_run",
61+ ChecksOp::GetCheckRun => "get_check_run",
62+ ChecksOp::ListCheckRunAnnotations => "list_check_run_annotations",
63+ ChecksOp::RerequestCheckRun => "rerequest_check_run",
64+ ChecksOp::ListCheckRunsForRef => "list_check_runs_for_ref",
65+ ChecksOp::ListCheckSuitesForRef => "list_check_suites_for_ref",
66+ ChecksOp::GetCheckSuite => "get_check_suite",
67+ ChecksOp::RerequestCheckSuite => "rerequest_check_suite",
68+ }
69+ }
70+
71+ /// For the API reference.
72+ pub fn title(self) -> &'static str {
73+ match self {
74+ ChecksOp::CreateCommitStatus => "Create a commit status",
75+ ChecksOp::ListCommitStatuses => "List a commit's statuses",
76+ ChecksOp::GetCombinedStatus => "Get a commit's combined status",
77+ ChecksOp::CreateCheckRun => "Create a check run",
78+ ChecksOp::UpdateCheckRun => "Update a check run",
79+ ChecksOp::GetCheckRun => "Get a check run",
80+ ChecksOp::ListCheckRunAnnotations => "List a check run's annotations",
81+ ChecksOp::RerequestCheckRun => "Rerequest a check run",
82+ ChecksOp::ListCheckRunsForRef => "List a commit's check runs",
83+ ChecksOp::ListCheckSuitesForRef => "List a commit's check suites",
84+ ChecksOp::GetCheckSuite => "Get a check suite",
85+ ChecksOp::RerequestCheckSuite => "Rerequest a check suite",
86+ }
87+ }
88+
89+ pub fn description(self) -> &'static str {
90+ match self {
91+ ChecksOp::CreateCommitStatus => "Set a status on a commit: state (pending, success, failure or error), context (what reports it, such as ci/build; default by default), description (at most 140 characters) and target_url (where to see more). A context has one status per commit: setting it again replaces it. A status is a check: a required check or a ruleset's required status check of its context is met by it. Needs the Write role; publishes status.created.",
92+ ChecksOp::ListCommitStatuses => "List the statuses on a commit, named by its SHA, a branch or a tag: one per context, newest first. Check runs are listed by list_check_runs_for_ref instead.",
93+ ChecksOp::GetCombinedStatus => "A commit's statuses, one per context, and the state they add up to: failure if any failed or errored, pending if any is pending or there are none, success otherwise. Named by its SHA, a branch or a tag.",
94+ ChecksOp::CreateCheckRun => "Report a check run on a commit: name and head_sha are required; status (queued, in_progress or completed; queued by default), conclusion (success, failure, neutral, cancelled, skipped, timed_out or action_required; it makes the run completed), started_at and completed_at (RFC 3339; filled in when left out), details_url (your page for it), external_id (your id for it), output (title, summary and text in Markdown, and up to 50 annotations: path, start_line, end_line, start_column, end_column, annotation_level notice, warning or failure, message, title, raw_details) and actions (up to 3 buttons: label, description, identifier). app names who reports it, by default your token's name. Runs are grouped per reporter and commit into a check suite. A check run is a check: a required check of its name is met by it, a cancelled one failing. Needs the Write role; publishes check_run.created, and check_run.completed when it is created completed.",
95+ ChecksOp::UpdateCheckRun => "Change a check run reported through the API, by id (cr_…). Fields left out stay as they are; output annotations are added to the ones it has (at most 1000 in all); actions, when given, replace its buttons. Giving a conclusion completes it. Publishes check_run.completed when it completes. A g1t Actions job's check run is its workflow's and cannot be changed.",
96+ ChecksOp::GetCheckRun => "Get a check run by id: cr_… for one reported through the API, or a g1t Actions job's id (job_…), whose workflow run is its suite and whose workflow says its name, run and event in workflow.",
97+ ChecksOp::ListCheckRunAnnotations => "List a check run's annotations in the order they were reported: path, start_line, end_line, start_column, end_column, annotation_level (notice, warning or failure), message, title and raw_details.",
98+ ChecksOp::RerequestCheckRun => "Ask for a check run to run again. For one reported through the API, its reporter is sent check_run.rerequested; for a g1t Actions job, its workflow run runs again (which also needs workflows:write). Needs the Write role.",
99+ ChecksOp::ListCheckRunsForRef => "List a commit's check runs, named by its SHA, a branch or a tag: those reported through the API and each job of its g1t Actions workflow runs. filter latest (the default) gives each name's latest run and each workflow's latest run per event; all gives every one. Narrow with check_name, status and app (a reporter's slug; actions for g1t Actions).",
100+ ChecksOp::ListCheckSuitesForRef => "List a commit's check suites, named by its SHA, a branch or a tag: one per reporter that reported check runs on it through the API, and one per g1t Actions workflow run, with its status and conclusion worked out from its latest check runs. Narrow with app and check_name.",
101+ ChecksOp::GetCheckSuite => "Get a check suite by id: cs_… for a reporter's, or a g1t Actions workflow run's id (run_…).",
102+ ChecksOp::RerequestCheckSuite => "Ask for a check suite to run again: its reporter is sent check_suite.rerequested, or a g1t Actions workflow run runs again (which also needs workflows:write). Needs the Write role.",
103+ }
104+ }
105+
106+ /// Whether it only reads, which anyone who can see the repository may.
107+ pub fn reads(self) -> bool {
108+ !matches!(
109+ self,
110+ ChecksOp::CreateCommitStatus
111+ | ChecksOp::CreateCheckRun
112+ | ChecksOp::UpdateCheckRun
113+ | ChecksOp::RerequestCheckRun
114+ | ChecksOp::RerequestCheckSuite
115+ )
116+ }
117+
118+ pub fn input(self) -> Value {
119+ let repo = || json!({ "type": "string", "description": "Repository as \"owner/name\", e.g. \"flagon-io/hello\"." });
120+ let git_ref = || json!({ "type": "string", "description": "The commit: its SHA, a branch or a tag." });
121+ let run_id = || json!({ "type": "string", "description": "The check run's id: cr_…, or a g1t Actions job's job_…" });
122+ let suite_id = || json!({ "type": "string", "description": "The check suite's id: cs_…, or a g1t Actions run's run_…" });
123+ let run_fields = |mut properties: Value, creating: bool| {
124+ properties["name"] = json!({ "type": "string", "description": "The check's name, at most 100 characters, such as lint or coverage." });
125+ if creating {
126+ properties["head_sha"] = json!({ "type": "string", "description": "The commit's full SHA (or a branch or tag, read as the commit it points to now)." });
127+ properties["app"] = json!({ "type": "string", "description": "Who reports it, shown with it and grouping its check suite: by default your token's name." });
128+ }
129+ properties["status"] = json!({ "type": "string", "enum": STATUSES, "description": "Where it is: queued, in_progress or completed." });
130+ properties["conclusion"] = json!({ "type": "string", "enum": CONCLUSIONS, "description": "How it came out; giving one completes it." });
131+ properties["started_at"] = json!({ "type": "string", "description": "When it started, RFC 3339." });
132+ properties["completed_at"] = json!({ "type": "string", "description": "When it completed, RFC 3339." });
133+ properties["details_url"] = json!({ "type": "string", "description": "Your page for it, http or https." });
134+ properties["external_id"] = json!({ "type": "string", "description": "Your id for it." });
135+ properties["output"] = json!({
136+ "type": "object",
137+ "description": "Its report: a title, a Markdown summary and text, and annotations on lines of files (at most 50 a request).",
138+ "properties": {
139+ "title": { "type": "string" },
140+ "summary": { "type": "string" },
141+ "text": { "type": "string" },
142+ "annotations": {
143+ "type": "array",
144+ "items": {
145+ "type": "object",
146+ "properties": {
147+ "path": { "type": "string" },
148+ "start_line": { "type": "integer" },
149+ "end_line": { "type": "integer" },
150+ "start_column": { "type": "integer" },
151+ "end_column": { "type": "integer" },
152+ "annotation_level": { "type": "string", "enum": ANNOTATION_LEVELS },
153+ "message": { "type": "string" },
154+ "title": { "type": "string" },
155+ "raw_details": { "type": "string" },
156+ },
157+ "required": ["path", "start_line", "end_line", "annotation_level", "message"],
158+ },
159+ },
160+ },
161+ });
162+ properties["actions"] = json!({
163+ "type": "array",
164+ "description": "Up to 3 buttons on its page. Pressing one sends you check_run.requested_action with its identifier.",
165+ "items": {
166+ "type": "object",
167+ "properties": {
168+ "label": { "type": "string", "description": "At most 20 characters." },
169+ "description": { "type": "string", "description": "At most 40 characters." },
170+ "identifier": { "type": "string", "description": "At most 20 characters." },
171+ },
172+ "required": ["label", "description", "identifier"],
173+ },
174+ });
175+ properties
176+ };
177+ let (properties, required): (Value, &[&str]) = match self {
178+ ChecksOp::CreateCommitStatus => (
179+ json!({
180+ "repo": repo(),
181+ "sha": { "type": "string", "description": "The commit's full SHA." },
182+ "state": { "type": "string", "enum": STATUS_STATES, "description": "pending, success, failure or error." },
183+ "context": { "type": "string", "description": "What reports it, such as ci/build; default when left out." },
184+ "description": { "type": "string", "description": "A short word on it, at most 140 characters." },
185+ "target_url": { "type": "string", "description": "Where to see more, http or https." },
186+ }),
187+ &["repo", "sha", "state"],
188+ ),
189+ ChecksOp::ListCommitStatuses | ChecksOp::GetCombinedStatus => (json!({ "repo": repo(), "ref": git_ref() }), &["repo", "ref"]),
190+ ChecksOp::CreateCheckRun => (run_fields(json!({ "repo": repo() }), true), &["repo", "name", "head_sha"]),
191+ ChecksOp::UpdateCheckRun => (run_fields(json!({ "repo": repo(), "id": run_id() }), false), &["repo", "id"]),
192+ ChecksOp::GetCheckRun | ChecksOp::ListCheckRunAnnotations | ChecksOp::RerequestCheckRun => {
193+ (json!({ "repo": repo(), "id": run_id() }), &["repo", "id"])
194+ }
195+ ChecksOp::ListCheckRunsForRef => (
196+ json!({
197+ "repo": repo(),
198+ "ref": git_ref(),
199+ "check_name": { "type": "string", "description": "Only runs of this name." },
200+ "status": { "type": "string", "enum": STATUSES, "description": "Only runs in this status." },
201+ "app": { "type": "string", "description": "Only this reporter's runs, by slug: actions for g1t Actions." },
202+ "filter": { "type": "string", "enum": ["latest", "all"], "description": "latest (the default) or all." },
203+ }),
204+ &["repo", "ref"],
205+ ),
206+ ChecksOp::ListCheckSuitesForRef => (
207+ json!({
208+ "repo": repo(),
209+ "ref": git_ref(),
210+ "app": { "type": "string", "description": "Only this reporter's suites, by slug." },
211+ "check_name": { "type": "string", "description": "Only suites with a run of this name." },
212+ }),
213+ &["repo", "ref"],
214+ ),
215+ ChecksOp::GetCheckSuite | ChecksOp::RerequestCheckSuite => (json!({ "repo": repo(), "id": suite_id() }), &["repo", "id"]),
216+ };
217+ json!({ "type": "object", "properties": properties, "required": required })
218+ }
219+}
220+
221+fn text(input: &Value, key: &str) -> Option<String> {
222+ match &input[key] {
223+ Value::String(text) => Some(text.trim().to_owned()).filter(|text| !text.is_empty()),
224+ _ => None,
225+ }
226+}
227+
228+/// A key in `camelCase`, as the contracts read it: `start_line` is `startLine`.
229+fn camel_key(key: &str) -> String {
230+ let mut out = String::with_capacity(key.len());
231+ let mut upper = false;
232+ for c in key.chars() {
233+ if c == '_' {
234+ upper = true;
235+ } else if upper {
236+ out.extend(c.to_uppercase());
237+ upper = false;
238+ } else {
239+ out.push(c);
240+ }
241+ }
242+ out
243+}
244+
245+fn camel(value: &Value) -> Value {
246+ match value {
247+ Value::Object(fields) => Value::Object(fields.iter().map(|(key, value)| (camel_key(key), camel(value))).collect::<Map<_, _>>()),
248+ Value::Array(items) => Value::Array(items.iter().map(camel).collect()),
249+ other => other.clone(),
250+ }
251+}
252+
253+/// A check run's fields from a request body.
254+pub(crate) fn run_input(input: &Value) -> std::result::Result<CheckRunInput, String> {
255+ const FIELDS: [&str; 10] =
256+ ["name", "head_sha", "status", "conclusion", "started_at", "completed_at", "details_url", "external_id", "output", "actions"];
257+ let mut fields = Map::new();
258+ for key in FIELDS {
259+ if let Some(value) = input.get(key).filter(|value| !value.is_null()) {
260+ fields.insert(camel_key(key), camel(value));
261+ }
262+ }
263+ serde_json::from_value(Value::Object(fields)).map_err(|error| format!("The check run could not be read: {error}"))
264+}
265+
266+/// Pages on the site, which the work service names by path, as full
267+/// addresses.
268+fn absolute(value: Value, site: &str) -> Value {
269+ match value {
270+ Value::Object(fields) => Value::Object(
271+ fields
272+ .into_iter()
273+ .map(|(key, value)| {
274+ let value = match value {
275+ Value::String(path) if matches!(key.as_str(), "htmlUrl" | "detailsUrl" | "targetUrl") && path.starts_with('/') => {
276+ Value::String(format!("{site}{path}"))
277+ }
278+ other => absolute(other, site),
279+ };
280+ (key, value)
281+ })
282+ .collect(),
283+ ),
284+ Value::Array(items) => Value::Array(items.into_iter().map(|item| absolute(item, site)).collect()),
285+ other => other,
286+ }
287+}
288+
289+async fn call<A: Serialize, T: DeserializeOwned + Serialize>(services: &Services, method: &str, args: &A) -> Result<Outcome<Value>> {
290+ let found: Outcome<T> = g1t_kit::call(&services.work, method, args).await?;
291+ Ok(match found {
292+ Outcome::Ok(value) => Outcome::Ok(absolute(serde_json::to_value(value)?, &services.addresses.site)),
293+ Outcome::Fail(failure) => Outcome::Fail(failure),
294+ })
295+}
296+
297+pub async fn run(op: ChecksOp, services: &Services, viewer: &Viewer, input: &Value) -> Result<Outcome<Value>> {
298+ let Some(repo) = crate::operations::repo_path(input) else {
299+ return Ok(Outcome::fail(FailureCode::Invalid, "Give the repository as \"owner/name\"."));
300+ };
301+ let actor = || viewer.clone().unwrap_or_default();
302+ let id = || text(input, "id").unwrap_or_default();
303+ let git_ref = || text(input, "ref").unwrap_or_default();
304+ match op {
305+ ChecksOp::CreateCommitStatus => {
306+ let args = CreateStatusArgs {
307+ actor: actor(),
308+ repo,
309+ sha: text(input, "sha").unwrap_or_default(),
310+ state: text(input, "state").unwrap_or_default(),
311+ context: text(input, "context"),
312+ description: text(input, "description"),
313+ target_url: text(input, "target_url"),
314+ };
315+ call::<_, g1t_contracts::work::CommitStatus>(services, "create_commit_status", &args).await
316+ }
317+ ChecksOp::ListCommitStatuses => {
318+ call::<_, Vec<g1t_contracts::work::CommitStatus>>(services, "commit_statuses", &RefArgs { viewer: viewer.clone(), repo, git_ref: git_ref() }).await
319+ }
320+ ChecksOp::GetCombinedStatus => {
321+ call::<_, CombinedStatus>(services, "combined_status", &RefArgs { viewer: viewer.clone(), repo, git_ref: git_ref() }).await
322+ }
323+ ChecksOp::CreateCheckRun | ChecksOp::UpdateCheckRun => {
324+ let run = match run_input(input) {
325+ Ok(run) => run,
326+ Err(message) => return Ok(Outcome::fail(FailureCode::Invalid, message)),
327+ };
328+ if op == ChecksOp::CreateCheckRun {
329+ let args = CreateCheckRunArgs { actor: actor(), repo, app: text(input, "app"), run };
330+ call::<_, CommitCheckRun>(services, "create_check_run", &args).await
331+ } else {
332+ call::<_, CommitCheckRun>(services, "update_check_run", &UpdateCheckRunArgs { actor: actor(), repo, id: id(), run }).await
333+ }
334+ }
335+ ChecksOp::GetCheckRun => call::<_, CommitCheckRun>(services, "get_check_run", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await,
336+ ChecksOp::ListCheckRunAnnotations => {
337+ call::<_, Vec<CheckAnnotation>>(services, "check_run_annotations", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await
338+ }
339+ ChecksOp::GetCheckSuite => call::<_, CommitCheckSuite>(services, "get_check_suite", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await,
340+ ChecksOp::RerequestCheckRun | ChecksOp::RerequestCheckSuite => {
341+ let method = if op == ChecksOp::RerequestCheckRun { "rerequest_check_run" } else { "rerequest_check_suite" };
342+ let done: Outcome<bool> = g1t_kit::call(&services.work, method, &RerequestArgs { actor: actor(), repo, id: id() }).await?;
343+ Ok(match done {
344+ Outcome::Ok(_) => Outcome::Ok(json!({ "rerequested": true })),
345+ Outcome::Fail(failure) => Outcome::Fail(failure),
346+ })
347+ }
348+ ChecksOp::ListCheckRunsForRef => {
349+ let args = RefCheckRunsArgs {
350+ viewer: viewer.clone(),
351+ repo,
352+ git_ref: git_ref(),
353+ check_name: text(input, "check_name"),
354+ status: text(input, "status"),
355+ app: text(input, "app"),
356+ filter: text(input, "filter"),
357+ };
358+ call::<_, CheckRunList>(services, "ref_check_runs", &args).await
359+ }
360+ ChecksOp::ListCheckSuitesForRef => {
361+ let args = RefCheckSuitesArgs { viewer: viewer.clone(), repo, git_ref: git_ref(), app: text(input, "app"), check_name: text(input, "check_name") };
362+ call::<_, CheckSuiteList>(services, "ref_check_suites", &args).await
363+ }
364+ }
365+}
366+
367+#[cfg(test)]
368+mod tests {
369+ use super::*;
370+
371+ #[test]
372+ fn a_body_in_snake_case_is_a_check_run() {
373+ let body = json!({
374+ "repo": "acme/web",
375+ "name": "lint",
376+ "head_sha": "a".repeat(40),
377+ "status": "completed",
378+ "conclusion": "failure",
379+ "output": {
380+ "title": "2 problems",
381+ "summary": "**2** problems",
382+ "annotations": [{ "path": "src/a.rs", "start_line": 3, "end_line": 3, "annotation_level": "warning", "message": "unused" }]
383+ },
384+ "actions": [{ "label": "Fix", "description": "Fix it", "identifier": "fix" }]
385+ });
386+ let run = run_input(&body).unwrap();
387+ assert_eq!(run.head_sha.as_deref(), Some("a".repeat(40).as_str()));
388+ let output = run.output.unwrap();
389+ assert_eq!(output.annotations[0].start_line, 3);
390+ assert_eq!(output.annotations[0].annotation_level, "warning");
391+ assert_eq!(run.actions.unwrap()[0].identifier, "fix");
392+ assert!(run_input(&json!({ "output": { "annotations": "no" } })).is_err());
393+ }
394+
395+ #[test]
396+ fn pages_on_the_site_become_full_addresses() {
397+ let value = json!({ "checkRuns": [{ "htmlUrl": "/acme/web/checks/cr_1", "detailsUrl": "https://ci.example.com/1", "name": "/x" }] });
398+ let out = absolute(value, "https://g1t.sh");
399+ assert_eq!(out["checkRuns"][0]["htmlUrl"], "https://g1t.sh/acme/web/checks/cr_1");
400+ assert_eq!(out["checkRuns"][0]["detailsUrl"], "https://ci.example.com/1");
401+ assert_eq!(out["checkRuns"][0]["name"], "/x");
402+ }
403+
404+ #[test]
405+ fn each_operation_is_described_with_a_schema() {
406+ for op in ChecksOp::ALL {
407+ assert!(crate::operations::Op::ALL.contains(&crate::operations::Op::Checks(op)), "{}", op.name());
408+ assert!(!op.title().is_empty() && op.description().len() > 40, "{}", op.name());
409+ assert!(op.input()["required"].as_array().unwrap().contains(&json!("repo")), "{}", op.name());
410+ assert_eq!(op.reads(), g1t_contracts::scopes::scope_for(op.name()).unwrap().level() == g1t_contracts::scopes::Level::Read, "{}", op.name());
411+ }
412+ }
413+}
+1−0
1010 mod audit;
1111 mod billing;
1212 mod blobs;
13+mod checks;
1314 mod deployments;
1415 mod mcp;
1516 mod notifications;
+26−3
1010 use crate::about::AboutOp;
1111 use crate::deployments::DeploymentsOp;
1212 use crate::operations::Op;
13+use crate::checks::ChecksOp;
1314 use crate::rules::RulesOp;
1415 use crate::security::SecurityOp;
1516 use crate::rest::{ROUTES, Route};
255256 ],
256257 ),
257258 (
259+ "Checks",
260+ "What CI, integrations and g1t Actions say about a commit, in the shapes CI tools already send: statuses (a state per context) and check runs (a lifecycle, a conclusion, a Markdown report, annotations on lines and buttons), grouped per reporter into check suites. g1t Actions jobs are check runs too. Required checks are met by either.",
261+ &[
262+ Op::Checks(ChecksOp::CreateCommitStatus),
263+ Op::Checks(ChecksOp::ListCommitStatuses),
264+ Op::Checks(ChecksOp::GetCombinedStatus),
265+ Op::Checks(ChecksOp::CreateCheckRun),
266+ Op::Checks(ChecksOp::UpdateCheckRun),
267+ Op::Checks(ChecksOp::GetCheckRun),
268+ Op::Checks(ChecksOp::ListCheckRunAnnotations),
269+ Op::Checks(ChecksOp::RerequestCheckRun),
270+ Op::Checks(ChecksOp::ListCheckRunsForRef),
271+ Op::Checks(ChecksOp::ListCheckSuitesForRef),
272+ Op::Checks(ChecksOp::GetCheckSuite),
273+ Op::Checks(ChecksOp::RerequestCheckSuite),
274+ ],
275+ ),
276+ (
258277 "Issues",
259278 "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.",
260279 &[
611630 Op::GetCodeownersErrors => "List CODEOWNERS errors",
612631 Op::Security(op) => op.title(),
613632 Op::Rules(op) => op.title(),
633+ Op::Checks(op) => op.title(),
614634 Op::About(op) => op.title(),
615635 Op::Deployments(op) => op.title(),
616636 }
748768 fn operation(route: &Route) -> Value {
749769 let op = route.op;
750770 let path_params: Vec<&str> = route.params().collect();
751− // `owner` and `name` in the path stand for the operation's `repo` input.
752− let covered = |name: &str| name == "repo" || path_params.contains(&name);
771+ // `owner` and `name` in the path stand for the operation's `repo` input,
772+ // so a `name` in the body, such as a check run's, is the body's own.
773+ let stands_for_repo =
774+ |name: &str| matches!(name, "owner" | "name") && path_params.contains(&"owner") && path_params.contains(&"name");
775+ let covered = |name: &str| name == "repo" || (path_params.contains(&name) && !stands_for_repo(name));
753776 let all_properties = op.properties();
754777 let mut properties = all_properties.clone();
755778 properties.retain(|name, _| !covered(name));
761784
762785 let mut parameters: Vec<Value> = path_params
763786 .iter()
764− .map(|name| parameter(name, "path", true, all_properties.get(*name)))
787+ .map(|name| parameter(name, "path", true, if stands_for_repo(name) { None } else { all_properties.get(*name) }))
765788 .collect();
766789 let mut body = Value::Null;
767790 if route.method == "GET" {
+24−1
2626 };
2727
2828 use crate::alerts::{AlertKind, SecurityAlert};
29+use crate::checks::ChecksOp;
2930 use crate::about::AboutOp;
3031 use crate::deployments::DeploymentsOp;
3132 use crate::rules::RulesOp;
276277 Security(SecurityOp),
277278 /// Rulesets: rules.rs.
278279 Rules(RulesOp),
280+ /// Statuses, check runs and check suites on commits: checks.rs.
281+ Checks(ChecksOp),
279282 /// A repository's languages, contributors, license, stars and releases: about.rs.
280283 About(AboutOp),
281284 /// Deployments wherever they run, and environments: deployments.rs.
642645 }
643646
644647 impl Op {
645− pub const ALL: [Op; 245] = [
648+ pub const ALL: [Op; 257] = [
646649 Op::Whoami,
647650 Op::GetWorkspace,
648651 Op::CreateWorkspace,
866869 Op::Rules(RulesOp::UpdateWorkspaceRuleset),
867870 Op::Rules(RulesOp::DeleteWorkspaceRuleset),
868871 Op::Rules(RulesOp::ListWorkspaceRuleEvaluations),
872+ Op::Checks(ChecksOp::CreateCommitStatus),
873+ Op::Checks(ChecksOp::ListCommitStatuses),
874+ Op::Checks(ChecksOp::GetCombinedStatus),
875+ Op::Checks(ChecksOp::CreateCheckRun),
876+ Op::Checks(ChecksOp::UpdateCheckRun),
877+ Op::Checks(ChecksOp::GetCheckRun),
878+ Op::Checks(ChecksOp::ListCheckRunAnnotations),
879+ Op::Checks(ChecksOp::RerequestCheckRun),
880+ Op::Checks(ChecksOp::ListCheckRunsForRef),
881+ Op::Checks(ChecksOp::ListCheckSuitesForRef),
882+ Op::Checks(ChecksOp::GetCheckSuite),
883+ Op::Checks(ChecksOp::RerequestCheckSuite),
869884 Op::About(AboutOp::GetLanguages),
870885 Op::About(AboutOp::ListContributors),
871886 Op::About(AboutOp::GetLicense),
10781093 Op::GetCodeownersErrors => "get_codeowners_errors",
10791094 Op::Security(op) => op.name(),
10801095 Op::Rules(op) => op.name(),
1096+ Op::Checks(op) => op.name(),
10811097 Op::About(op) => op.name(),
10821098 Op::Deployments(op) => op.name(),
10831099 }
15901606 }
15911607 Op::Security(op) => op.description(),
15921608 Op::Rules(op) => op.description(),
1609+ Op::Checks(op) => op.description(),
15931610 Op::About(op) => op.description(),
15941611 Op::Deployments(op) => op.description(),
15951612 }
29602977 ),
29612978 Op::Security(op) => op.input(),
29622979 Op::Rules(op) => op.input(),
2980+ Op::Checks(op) => op.input(),
29632981 Op::About(op) => op.input(),
29642982 Op::Deployments(op) => op.input(),
29652983 }
29672985
29682986 /// Whether the operation refuses an anonymous caller outright.
29692987 pub(crate) fn needs_user(self) -> bool {
2988+ // A public repository's checks are anyone's to read.
2989+ if let Op::Checks(op) = self {
2990+ return !op.reads();
2991+ }
29702992 if let Op::About(op) = self {
29712993 return !op.anonymous();
29722994 }
50145036 // each answer its public shape.
50155037 Op::Security(op) => crate::security::run(op, services, viewer, input).await,
50165038 Op::Rules(op) => crate::rules::run(op, services, viewer, input).await,
5039+ Op::Checks(op) => crate::checks::run(op, services, viewer, input).await,
50175040 Op::About(op) => crate::about::run(op, services, viewer, input).await,
50185041 Op::Deployments(op) => crate::deployments::run(op, services, viewer, input).await,
50195042 Op::ReopenSecurityAlert => {
+398−0
1022710227 "response": {
1022810228 "deleted": true
1022910229 }
10230+ },
10231+ "create_commit_status": {
10232+ "params": {
10233+ "owner": "flagon-io",
10234+ "name": "hello",
10235+ "sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e"
10236+ },
10237+ "request": {
10238+ "state": "success",
10239+ "context": "ci/build",
10240+ "description": "Build #4821 passed",
10241+ "target_url": "https://ci.example.com/builds/4821"
10242+ },
10243+ "response": {
10244+ "context": "ci/build",
10245+ "state": "success",
10246+ "description": "Build #4821 passed",
10247+ "target_url": "https://ci.example.com/builds/4821",
10248+ "updated_at": "2026-10-07T14:03:02.118Z",
10249+ "source": "api"
10250+ },
10251+ "notes": "Setting a context again replaces its status on that commit. A status counts as a check: a required check named `ci/build`, in branch protection or a ruleset's `required_status_checks`, is met by it, and a ruleset check pinned to the `api` integration only by statuses and check runs reported through the API. See the [Checks guide](/guides/checks/)."
10252+ },
10253+ "list_commit_statuses": {
10254+ "params": {
10255+ "owner": "flagon-io",
10256+ "name": "hello",
10257+ "ref": "main"
10258+ },
10259+ "response": [
10260+ {
10261+ "context": "ci/build",
10262+ "state": "success",
10263+ "description": "Build #4821 passed",
10264+ "target_url": "https://ci.example.com/builds/4821",
10265+ "updated_at": "2026-10-07T14:03:02.118Z",
10266+ "source": "api"
10267+ }
10268+ ]
10269+ },
10270+ "get_combined_status": {
10271+ "params": {
10272+ "owner": "flagon-io",
10273+ "name": "hello",
10274+ "ref": "main"
10275+ },
10276+ "response": {
10277+ "state": "success",
10278+ "sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10279+ "total_count": 1,
10280+ "statuses": [
10281+ {
10282+ "context": "ci/build",
10283+ "state": "success",
10284+ "description": "Build #4821 passed",
10285+ "target_url": "https://ci.example.com/builds/4821",
10286+ "updated_at": "2026-10-07T14:03:02.118Z",
10287+ "source": "api"
10288+ }
10289+ ]
10290+ }
10291+ },
10292+ "create_check_run": {
10293+ "params": {
10294+ "owner": "flagon-io",
10295+ "name": "hello"
10296+ },
10297+ "request": {
10298+ "name": "lint",
10299+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10300+ "status": "completed",
10301+ "conclusion": "failure",
10302+ "details_url": "https://ci.example.com/builds/4821",
10303+ "external_id": "4821",
10304+ "output": {
10305+ "title": "2 problems",
10306+ "summary": "**2** problems in 1 file.",
10307+ "annotations": [
10308+ {
10309+ "path": "src/parse.rs",
10310+ "start_line": 42,
10311+ "end_line": 42,
10312+ "annotation_level": "warning",
10313+ "message": "unused variable: `depth`"
10314+ }
10315+ ]
10316+ },
10317+ "actions": [
10318+ {
10319+ "label": "Fix this",
10320+ "description": "Apply the suggested fixes",
10321+ "identifier": "fix"
10322+ }
10323+ ]
10324+ },
10325+ "response": {
10326+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10327+ "name": "lint",
10328+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10329+ "status": "completed",
10330+ "conclusion": "failure",
10331+ "started_at": "2026-10-07T14:02:11.000Z",
10332+ "completed_at": "2026-10-07T14:02:36.000Z",
10333+ "details_url": "https://ci.example.com/builds/4821",
10334+ "external_id": "4821",
10335+ "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10336+ "output": {
10337+ "title": "2 problems",
10338+ "summary": "**2** problems in 1 file.",
10339+ "text": null,
10340+ "annotations_count": 2
10341+ },
10342+ "actions": [
10343+ {
10344+ "label": "Fix this",
10345+ "description": "Apply the suggested fixes",
10346+ "identifier": "fix"
10347+ }
10348+ ],
10349+ "check_suite": {
10350+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p"
10351+ },
10352+ "app": {
10353+ "slug": "buildkite",
10354+ "name": "Buildkite"
10355+ },
10356+ "created_at": "2026-10-07T14:02:11.318Z"
10357+ },
10358+ "notes": "Start a run as `queued` or `in_progress` and complete it later with [`update_check_run`](/reference/api/checks/update-check-run/), or create it completed. `app` names who reports it; by default it is your token's name, and a g1t Actions job's `G1T_TOKEN` reports as g1t Actions. Each reporter's runs on a commit form one check suite. The run also stands as a status of its name, so a required check `lint` is met by it: `success`, `neutral` and `skipped` pass, any other conclusion fails. See the [Checks guide](/guides/checks/)."
10359+ },
10360+ "update_check_run": {
10361+ "params": {
10362+ "owner": "flagon-io",
10363+ "name": "hello",
10364+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n"
10365+ },
10366+ "request": {
10367+ "conclusion": "success",
10368+ "output": {
10369+ "title": "No problems",
10370+ "summary": "All clear."
10371+ }
10372+ },
10373+ "response": {
10374+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10375+ "name": "lint",
10376+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10377+ "status": "completed",
10378+ "conclusion": "success",
10379+ "started_at": "2026-10-07T14:02:11.000Z",
10380+ "completed_at": "2026-10-07T14:02:36.000Z",
10381+ "details_url": "https://ci.example.com/builds/4821",
10382+ "external_id": "4821",
10383+ "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10384+ "output": {
10385+ "title": "No problems",
10386+ "summary": "All clear.",
10387+ "text": null,
10388+ "annotations_count": 2
10389+ },
10390+ "actions": [
10391+ {
10392+ "label": "Fix this",
10393+ "description": "Apply the suggested fixes",
10394+ "identifier": "fix"
10395+ }
10396+ ],
10397+ "check_suite": {
10398+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p"
10399+ },
10400+ "app": {
10401+ "slug": "buildkite",
10402+ "name": "Buildkite"
10403+ },
10404+ "created_at": "2026-10-07T14:02:11.318Z"
10405+ },
10406+ "notes": "Annotations given here are added to the ones the run has, at most 50 a request and 1000 in all."
10407+ },
10408+ "get_check_run": {
10409+ "params": {
10410+ "owner": "flagon-io",
10411+ "name": "hello",
10412+ "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k"
10413+ },
10414+ "response": {
10415+ "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10416+ "name": "Test",
10417+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10418+ "status": "completed",
10419+ "conclusion": "success",
10420+ "started_at": "2026-10-07T14:01:02.000Z",
10421+ "completed_at": "2026-10-07T14:01:27.000Z",
10422+ "details_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10423+ "external_id": null,
10424+ "html_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10425+ "output": {
10426+ "title": null,
10427+ "summary": null,
10428+ "text": null,
10429+ "annotations_count": 0
10430+ },
10431+ "actions": [],
10432+ "check_suite": {
10433+ "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m"
10434+ },
10435+ "app": {
10436+ "slug": "actions",
10437+ "name": "g1t Actions"
10438+ },
10439+ "workflow": {
10440+ "run_id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m",
10441+ "name": "CI",
10442+ "event": "push"
10443+ },
10444+ "created_at": "2026-10-07T14:01:00.512Z"
10445+ }
10446+ },
10447+ "list_check_run_annotations": {
10448+ "params": {
10449+ "owner": "flagon-io",
10450+ "name": "hello",
10451+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n"
10452+ },
10453+ "response": [
10454+ {
10455+ "path": "src/parse.rs",
10456+ "start_line": 42,
10457+ "end_line": 42,
10458+ "start_column": 9,
10459+ "end_column": 14,
10460+ "annotation_level": "warning",
10461+ "message": "unused variable: `depth`",
10462+ "title": "unused_variables",
10463+ "raw_details": "#[warn(unused_variables)] on by default"
10464+ }
10465+ ]
10466+ },
10467+ "rerequest_check_run": {
10468+ "params": {
10469+ "owner": "flagon-io",
10470+ "name": "hello",
10471+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n"
10472+ },
10473+ "response": {
10474+ "rerequested": true
10475+ }
10476+ },
10477+ "list_check_runs_for_ref": {
10478+ "params": {
10479+ "owner": "flagon-io",
10480+ "name": "hello",
10481+ "ref": "main"
10482+ },
10483+ "query": {
10484+ "filter": "latest"
10485+ },
10486+ "response": {
10487+ "total_count": 2,
10488+ "check_runs": [
10489+ {
10490+ "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10491+ "name": "lint",
10492+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10493+ "status": "completed",
10494+ "conclusion": "failure",
10495+ "started_at": "2026-10-07T14:02:11.000Z",
10496+ "completed_at": "2026-10-07T14:02:36.000Z",
10497+ "details_url": "https://ci.example.com/builds/4821",
10498+ "external_id": "4821",
10499+ "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n",
10500+ "output": {
10501+ "title": "2 problems",
10502+ "summary": "**2** problems in 1 file.",
10503+ "text": null,
10504+ "annotations_count": 2
10505+ },
10506+ "actions": [
10507+ {
10508+ "label": "Fix this",
10509+ "description": "Apply the suggested fixes",
10510+ "identifier": "fix"
10511+ }
10512+ ],
10513+ "check_suite": {
10514+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p"
10515+ },
10516+ "app": {
10517+ "slug": "buildkite",
10518+ "name": "Buildkite"
10519+ },
10520+ "created_at": "2026-10-07T14:02:11.318Z"
10521+ },
10522+ {
10523+ "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10524+ "name": "Test",
10525+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10526+ "status": "completed",
10527+ "conclusion": "success",
10528+ "started_at": "2026-10-07T14:01:02.000Z",
10529+ "completed_at": "2026-10-07T14:01:27.000Z",
10530+ "details_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10531+ "external_id": null,
10532+ "html_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k",
10533+ "output": {
10534+ "title": null,
10535+ "summary": null,
10536+ "text": null,
10537+ "annotations_count": 0
10538+ },
10539+ "actions": [],
10540+ "check_suite": {
10541+ "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m"
10542+ },
10543+ "app": {
10544+ "slug": "actions",
10545+ "name": "g1t Actions"
10546+ },
10547+ "workflow": {
10548+ "run_id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m",
10549+ "name": "CI",
10550+ "event": "push"
10551+ },
10552+ "created_at": "2026-10-07T14:01:00.512Z"
10553+ }
10554+ ]
10555+ }
10556+ },
10557+ "list_check_suites_for_ref": {
10558+ "params": {
10559+ "owner": "flagon-io",
10560+ "name": "hello",
10561+ "ref": "main"
10562+ },
10563+ "response": {
10564+ "total_count": 2,
10565+ "check_suites": [
10566+ {
10567+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p",
10568+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10569+ "head_branch": "main",
10570+ "status": "completed",
10571+ "conclusion": "failure",
10572+ "app": {
10573+ "slug": "buildkite",
10574+ "name": "Buildkite"
10575+ },
10576+ "latest_check_runs_count": 1,
10577+ "created_at": "2026-10-07T14:02:11.318Z",
10578+ "updated_at": "2026-10-07T14:02:36.502Z"
10579+ },
10580+ {
10581+ "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m",
10582+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10583+ "head_branch": "main",
10584+ "status": "completed",
10585+ "conclusion": "success",
10586+ "app": {
10587+ "slug": "actions",
10588+ "name": "g1t Actions"
10589+ },
10590+ "name": "CI",
10591+ "latest_check_runs_count": 1,
10592+ "created_at": "2026-10-07T14:01:00.512Z",
10593+ "updated_at": "2026-10-07T14:01:28.040Z"
10594+ }
10595+ ]
10596+ }
10597+ },
10598+ "get_check_suite": {
10599+ "params": {
10600+ "owner": "flagon-io",
10601+ "name": "hello",
10602+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p"
10603+ },
10604+ "response": {
10605+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p",
10606+ "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e",
10607+ "head_branch": "main",
10608+ "status": "completed",
10609+ "conclusion": "failure",
10610+ "app": {
10611+ "slug": "buildkite",
10612+ "name": "Buildkite"
10613+ },
10614+ "latest_check_runs_count": 1,
10615+ "created_at": "2026-10-07T14:02:11.318Z",
10616+ "updated_at": "2026-10-07T14:02:36.502Z"
10617+ }
10618+ },
10619+ "rerequest_check_suite": {
10620+ "params": {
10621+ "owner": "flagon-io",
10622+ "name": "hello",
10623+ "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p"
10624+ },
10625+ "response": {
10626+ "rerequested": true
10627+ }
1023010628 }
1023110629 }
+12−1
77 //! encoded again, so that every field the type has is sent, not only the
88 //! ones an example shows.
99
10−use g1t_contracts::{access, actions, codeowners, integrations, repos, rules, search, teams, webhooks, work};
10+use g1t_contracts::{access, actions, checks, codeowners, integrations, repos, rules, search, teams, webhooks, work};
1111 use g1t_kit::wire::{self, USER_KEYED};
1212 use serde::Serialize;
1313 use serde::de::DeserializeOwned;
1515
1616 use crate::openapi::document;
1717 use crate::operations::Op;
18+use crate::checks::ChecksOp;
1819 use crate::rules::RulesOp;
1920
2021 /// A key as `#[serde(rename_all = "camelCase")]` writes it.
198199 Op::GetThreadSubscription | Op::SetThreadSubscription | Op::DeleteThreadSubscription => {
199200 through::<g1t_contracts::inbox::ThreadSubscription>(op, sent)
200201 }
202+ Op::Checks(ChecksOp::CreateCommitStatus) => through::<work::CommitStatus>(op, sent),
203+ Op::Checks(ChecksOp::ListCommitStatuses) => through::<Vec<work::CommitStatus>>(op, sent),
204+ Op::Checks(ChecksOp::GetCombinedStatus) => through::<checks::CombinedStatus>(op, sent),
205+ Op::Checks(ChecksOp::CreateCheckRun | ChecksOp::UpdateCheckRun | ChecksOp::GetCheckRun) => {
206+ through::<checks::CommitCheckRun>(op, sent)
207+ }
208+ Op::Checks(ChecksOp::ListCheckRunAnnotations) => through::<Vec<checks::CheckAnnotation>>(op, sent),
209+ Op::Checks(ChecksOp::ListCheckRunsForRef) => through::<checks::CheckRunList>(op, sent),
210+ Op::Checks(ChecksOp::ListCheckSuitesForRef) => through::<checks::CheckSuiteList>(op, sent),
211+ Op::Checks(ChecksOp::GetCheckSuite) => through::<checks::CommitCheckSuite>(op, sent),
201212 _ => sent,
202213 }
203214 }
+48−0
55 use crate::about::AboutOp;
66 use crate::deployments::DeploymentsOp;
77 use crate::operations::Op;
8+use crate::checks::ChecksOp;
89 use crate::rules::RulesOp;
910 use crate::security::SecurityOp;
1011
261262 &[],
262263 ),
263264 route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]),
265+ // Checks: GitHub's addresses for statuses, check runs and check suites.
266+ route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]),
267+ route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]),
268+ route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]),
269+ route(
270+ "GET",
271+ "/repos/:owner/:name/commits/:ref/check-runs",
272+ Op::Checks(ChecksOp::ListCheckRunsForRef),
273+ &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")],
274+ ),
275+ route(
276+ "GET",
277+ "/repos/:owner/:name/commits/:ref/check-suites",
278+ Op::Checks(ChecksOp::ListCheckSuitesForRef),
279+ &[("app", "app"), ("check_name", "check_name")],
280+ ),
281+ route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]),
282+ route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]),
283+ route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]),
284+ route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]),
285+ route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]),
286+ route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]),
287+ route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]),
264288 // Rulesets: a repository's, a workspace's, the rules of one branch,
265289 // and how they judged pushes and merges.
266290 route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]),
11571181 }
11581182
11591183 #[test]
1184+ fn checks_are_at_githubs_addresses() {
1185+ let sha = "a".repeat(40);
1186+ let body = json!({ "state": "success", "context": "ci/build" });
1187+ let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap();
1188+ assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus));
1189+ assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" }));
1190+ let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap();
1191+ assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus));
1192+ assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" }));
1193+ let query = [("check_name".to_owned(), "lint".to_owned())];
1194+ let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap();
1195+ assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef));
1196+ assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" }));
1197+ let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap();
1198+ assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun));
1199+ assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" }));
1200+ let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op;
1201+ assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun));
1202+ assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations));
1203+ assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite));
1204+ assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef));
1205+ }
1206+
1207+ #[test]
11601208 fn query_parameters_are_renamed() {
11611209 let query = [
11621210 ("q".to_owned(), "parser".to_owned()),
+14−1
2121 use crate::about::AboutOp;
2222 use crate::deployments::DeploymentsOp;
2323 use crate::operations::Op;
24+use crate::checks::ChecksOp;
2425 use crate::rules::RulesOp;
2526 use crate::security::SecurityOp;
2627
199200 Tool {
200201 name: "workflow",
201202 title: "Workflows",
202− description: "GitHub Actions workflows from .g1t/workflows: their runs, jobs and logs, and running, cancelling or rerunning them. Deployments wherever they run (reported from any CI, made by jobs with an `environment:`, or built on g1t.page), their statuses and environments, and reporting your own. Also the self-hosted runners they run on: a workspace's (`workspace`) or a repository's own (`repo`), their groups, and where agent work runs.",
203+ description: "GitHub Actions workflows from .g1t/workflows: their runs, jobs and logs, and running, cancelling or rerunning them. Deployments wherever they run (reported from any CI, made by jobs with an `environment:`, or built on g1t.page), their statuses and environments, and reporting your own. Checks on commits: statuses, check runs (a g1t Actions job is one) and check suites, to read where a commit stands or report on it from CI or an integration. Also the self-hosted runners they run on: a workspace's (`workspace`) or a repository's own (`repo`), their groups, and where agent work runs.",
203204 default_action: None,
204205 actions: &[
205206 a("list", Op::ListWorkflows, "Workflows on the default branch"),
210211 a("cancel", Op::CancelWorkflowRun, "Cancel a run"),
211212 a("rerun", Op::RerunWorkflowRun, "Run a finished run again"),
212213 a("update", Op::UpdateWorkflow, "Turn a workflow on or off"),
214+ a("combined_status", Op::Checks(ChecksOp::GetCombinedStatus), "A commit's statuses and the state they add up to"),
215+ a("list_statuses", Op::Checks(ChecksOp::ListCommitStatuses), "A commit's statuses, newest first"),
216+ a("set_status", Op::Checks(ChecksOp::CreateCommitStatus), "Set a status on a commit"),
217+ a("list_check_runs", Op::Checks(ChecksOp::ListCheckRunsForRef), "A commit's check runs, g1t Actions jobs included"),
218+ a("get_check_run", Op::Checks(ChecksOp::GetCheckRun), "One check run with its report"),
219+ a("check_run_annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), "What a check run says about lines of files"),
220+ a("create_check_run", Op::Checks(ChecksOp::CreateCheckRun), "Report a check run on a commit"),
221+ a("update_check_run", Op::Checks(ChecksOp::UpdateCheckRun), "Move a check run on, complete it, add annotations"),
222+ a("rerequest_check_run", Op::Checks(ChecksOp::RerequestCheckRun), "Ask for a check run to run again"),
223+ a("list_check_suites", Op::Checks(ChecksOp::ListCheckSuitesForRef), "A commit's check suites, one per reporter or workflow run"),
224+ a("get_check_suite", Op::Checks(ChecksOp::GetCheckSuite), "One check suite"),
225+ a("rerequest_check_suite", Op::Checks(ChecksOp::RerequestCheckSuite), "Ask for a check suite to run again"),
213226 a("list_deployments", Op::Deployments(DeploymentsOp::ListDeployments), "Deployments wherever they run, newest first, filtered"),
214227 a("get_deployment", Op::Deployments(DeploymentsOp::GetDeployment), "One deployment with every status it has had"),
215228 a("create_deployment", Op::Deployments(DeploymentsOp::CreateDeployment), "Report a deployment of a ref to an environment"),
+1−0
126126 label: 'Landing changes',
127127 items: [
128128 { label: 'Pull requests and checks', slug: 'guides/pull-requests' },
129+ { label: 'Checks', slug: 'guides/checks' },
129130 { label: 'Pull requests into other branches', slug: 'guides/base-branches' },
130131 { label: 'Labels', slug: 'guides/labels' },
131132 { label: 'Milestones', slug: 'guides/milestones' },
+3−1
209209 `pull_request` runs on every pull request's head, whoever opened it, a
210210 person or an agent, and its runs report a check named after the workflow:
211211 a workflow with `name: CI` reports `CI`, with the status context
212−`CI / pull_request` (the workflow's name and the event).
212+`CI / pull_request` (the workflow's name and the event). Each of its jobs
213+is a [check run](/guides/checks/) on the commit, shown as
214+`CI / test (pull_request)` beside it wherever it appears.
213215
214216 - **Which checks a merge needs** is up to the [rules](/guides/rules/) of the branch it merges into,
215217 their [required status checks](/guides/pull-requests/#required-status-checks),
+4−1
321321 | Issues & pull requests | `issues:read`, `issues:write`, `pull_requests:read`, `pull_requests:write` |
322322 | Agents | `agents:run` |
323323 | Workflows | `workflows:read`, `workflows:write` |
324+| Checks | `checks:read`, `checks:write` |
324325 | Deployments | `deployments:read`, `deployments:write` |
325326 | Memory & search | `memory:read`, `memory:write` |
326327 | Account | `account:read`, `account:write` |
355356 | `agents:run` | Put g1t to work and message it, which uses the workspace's money |
356357 | `workflows:read` | Read workflows, runs and logs |
357358 | `workflows:write` | Run, cancel, rerun and turn workflows on or off |
359+| `checks:read` | Read commits' statuses, check runs, check suites and annotations |
360+| `checks:write` | Report [statuses and check runs](/guides/checks/) on commits, and ask for checks to run again |
358361 | `deployments:read` | See [deployments](/guides/deployments-api/), their statuses and environments |
359362 | `deployments:write` | Report deployments and their statuses, from any CI |
360363 | `memory:read` | Recall memory and search the workspace's context |
414417 | --- | --- |
415418 | Read only | Every `read` scope. Changes nothing. |
416419 | Agent | Every `read` scope except `runners:read`, and `code:write`, `issues:write`, `pull_requests:write`, `agents:run`, `memory:write` and `notifications:write`. Reads everything, works on issues and pull requests, pushes code, puts g1t to work, and answers your inbox. No admin scope. |
417−| CI | `repo:read`, `code:read`, `code:write`, `packages:read`, `packages:write`, `workflows:read`, `workflows:write`, `deployments:read` and `deployments:write`. Clones and pushes code, pushes and pulls packages, runs workflows and reports deployments. |
420+| CI | `repo:read`, `code:read`, `code:write`, `packages:read`, `packages:write`, `workflows:read`, `workflows:write`, `checks:read`, `checks:write`, `deployments:read` and `deployments:write`. Clones and pushes code, pushes and pulls packages, runs workflows, and reports [checks](/guides/checks/) and deployments. |
418421 | Full access | Everything you can do, including deleting repositories and changing who has access. Marked **Dangerous**. |
419422
420423 Admin scopes change things that are hard to undo, or decide who can reach
+300−0
1+---
2+title: Checks
3+description: Report what CI and integrations find on a commit as statuses and check runs, see them beside every commit, and require them before a merge.
4+---
5+
6+Every commit can carry checks: what your workflows, your CI, your
7+deployments and any integration say about it. g1t shows them beside the
8+commit wherever it appears, so you can tell at a glance whether a change
9+works and how far along it is.
10+
11+- A green check: every check passed.
12+- A red cross: at least one failed.
13+- An amber dot: at least one is still running or queued.
14+
15+Select the mark to see the list: a headline ("All checks have passed",
16+"Some checks were not successful" or "Some checks haven't completed yet"),
17+how many passed, failed and are running, and each check with how it went,
18+how long it took, and a **Details** link.
19+
20+## Where checks show
21+
22+| Page | Where |
23+| --- | --- |
24+| **Code** | Beside the latest commit, above the files |
25+| **Commits** | Beside each commit |
26+| A commit's page | Beside its hash |
27+| **Branches** | Beside each branch's latest commit |
28+| **Tags** | Beside each tagged commit |
29+| A pull request | Beside its head commit, in the sidebar; its merge box lists them too |
30+| A project's overview | Beside the latest commit, and each active branch |
31+
32+A page reads the checks of all its commits at once, after the page itself
33+has loaded: each mark shows a placeholder until they arrive.
34+
35+## Statuses and check runs
36+
37+There are two ways to report a check. Use either, or both.
38+
39+| | A status | A check run |
40+| --- | --- | --- |
41+| What it is | A state for one context on a commit, such as `ci/build` | One run of one check, with a life of its own |
42+| States | `pending`, `success`, `failure`, `error` | `status`: `queued`, `in_progress`, `completed`; once completed, a `conclusion`: `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out` or `action_required` |
43+| Report | A short `description` and a `target_url` | A `title`, a Markdown `summary` and `text`, up to 1,000 annotations on lines of files, and up to 3 buttons |
44+| On g1t | Listed with a link to `target_url` | Its own page under the repository, linked from the list |
45+| Set again | Replaces the context's status on that commit | Update the same run, or create a new one |
46+| Use it for | A quick pass or fail from any tool | Test, lint and scan results someone needs to read |
47+
48+Every job of a [workflow](/guides/actions/) run is a check run, named
49+`Workflow / job (event)` in the list, such as `CI / test (push)`, and its
50+**Details** opens the job's log. You do not report those: g1t does.
51+
52+## Report a status
53+
54+You need a token with the `checks:write` scope (see
55+[scopes](/guides/authentication/#scopes)) and the Write
56+[role](/guides/access-and-roles/) on the repository.
57+
58+```sh
59+curl -X POST https://api.g1t.sh/repos/<workspace>/<repo>/statuses/<sha> \
60+ -H "Authorization: Bearer $G1T_TOKEN" \
61+ -H "Content-Type: application/json" \
62+ -d '{
63+ "state": "success",
64+ "context": "ci/build",
65+ "description": "Build #4821 passed",
66+ "target_url": "https://ci.example.com/builds/4821"
67+ }'
68+```
69+
70+| Field | Required | What it is |
71+| --- | --- | --- |
72+| `state` | Yes | `pending`, `success`, `failure` or `error`. `error` counts as a failure. |
73+| `context` | No | What reports it, such as `ci/build`; `default` when left out. At most 100 characters. |
74+| `description` | No | A short word on it, at most 140 characters. |
75+| `target_url` | No | Where to see more: an `http` or `https` address. |
76+
77+[`create_commit_status`](/reference/api/checks/create-commit-status/)
78+returns the status. Set a context again to replace it: report `pending`
79+when a build starts, then `success` or `failure` when it ends.
80+[`get_combined_status`](/reference/api/checks/get-combined-status/) gives
81+a commit's statuses and what they add up to:
82+`GET /repos/<workspace>/<repo>/commits/<ref>/status`, where `<ref>` is a
83+commit SHA, a branch or a tag.
84+
85+## Report a check run
86+
87+A check run is reported in two steps: create it when the work starts,
88+then complete it.
89+
90+1. Create it, in progress:
91+
92+ ```sh
93+ curl -X POST https://api.g1t.sh/repos/<workspace>/<repo>/check-runs \
94+ -H "Authorization: Bearer $G1T_TOKEN" \
95+ -H "Content-Type: application/json" \
96+ -d '{"name": "lint", "head_sha": "<sha>", "status": "in_progress", "details_url": "https://ci.example.com/builds/4821"}'
97+ ```
98+
99+ The answer has its `id`, such as `cr_01kq4b7c8d9e0f1g2h3j4k5m6n`.
100+
101+2. Complete it with a `conclusion`, a report and annotations:
102+
103+ ```sh
104+ curl -X PATCH https://api.g1t.sh/repos/<workspace>/<repo>/check-runs/<id> \
105+ -H "Authorization: Bearer $G1T_TOKEN" \
106+ -H "Content-Type: application/json" \
107+ -d '{
108+ "conclusion": "failure",
109+ "output": {
110+ "title": "2 problems",
111+ "summary": "**2** problems in `src/parse.rs`.",
112+ "annotations": [
113+ {"path": "src/parse.rs", "start_line": 42, "end_line": 42, "annotation_level": "warning", "message": "unused variable: `depth`"},
114+ {"path": "src/parse.rs", "start_line": 57, "end_line": 60, "annotation_level": "failure", "message": "this match is not exhaustive"}
115+ ]
116+ }
117+ }'
118+ ```
119+
120+Giving a `conclusion` completes the run; `started_at` and `completed_at`
121+are filled in when you leave them out. A run can also be created already
122+completed, in one call.
123+
124+| Field | What it is |
125+| --- | --- |
126+| `name` | The check's name, at most 100 characters. Required to create one. |
127+| `head_sha` | The commit it is about. Required to create one. |
128+| `status` | `queued` (the default), `in_progress` or `completed`. |
129+| `conclusion` | `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out` or `action_required`. |
130+| `started_at`, `completed_at` | When it started and ended, in RFC 3339. |
131+| `details_url` | Your own page for it. |
132+| `external_id` | Your own id for it. |
133+| `output` | `title`, `summary` and `text` (Markdown, at most 65,535 characters each) and `annotations`. |
134+| `actions` | Up to 3 buttons: `label` (20 characters), `description` (40) and `identifier` (20). |
135+| `app` | Who reports it, such as `Codecov`. By default, your token's name. |
136+
137+### A minimal reporter
138+
139+This script runs a command and reports it as a check run, from any CI that
140+has `curl` and `jq`:
141+
142+```sh
143+#!/bin/sh
144+# check.sh <name> <command…>: report a command as a g1t check run.
145+set -u
146+name=$1; shift
147+api="https://api.g1t.sh/repos/$G1T_REPO"
148+auth="Authorization: Bearer $G1T_TOKEN"
149+
150+id=$(curl -sf -X POST "$api/check-runs" -H "$auth" -H "Content-Type: application/json" \
151+ -d "$(jq -n --arg name "$name" --arg sha "$G1T_SHA" '{name: $name, head_sha: $sha, status: "in_progress"}')" | jq -r .id)
152+
153+if output=$("$@" 2>&1); then conclusion=success; else conclusion=failure; fi
154+
155+curl -sf -X PATCH "$api/check-runs/$id" -H "$auth" -H "Content-Type: application/json" \
156+ -d "$(jq -n --arg c "$conclusion" --arg log "$(printf '%s' "$output" | tail -c 60000)" \
157+ '{conclusion: $c, output: {title: $c, summary: ("```\n" + $log + "\n```")}}')" > /dev/null
158+[ "$conclusion" = success ]
159+```
160+
161+```sh
162+G1T_REPO=acme/web G1T_SHA=$(git rev-parse HEAD) ./check.sh lint npm run lint
163+```
164+
165+### Annotations
166+
167+An annotation points at lines of a file at the run's commit: `path`,
168+`start_line` and `end_line` (from 1), and for one line, `start_column`
169+and `end_column`. `annotation_level` is `notice`, `warning` or `failure`;
170+`message` says what is wrong, `title` names it, and `raw_details` holds
171+anything longer.
172+
173+Send at most 50 in one request; each update adds to those the run has,
174+up to 1,000. On the check run's page they are grouped by file, each
175+linking to its lines.
176+[`list_check_run_annotations`](/reference/api/checks/list-check-run-annotations/)
177+returns them in the order they were reported.
178+
179+### Buttons
180+
181+`actions` puts up to 3 buttons on the check run's page, such as **Fix
182+this** or **Ignore**. When someone with the Write role presses one, g1t
183+sends your webhook a `check_run.requested_action` event with the button's
184+`identifier` in `data.requested_action`. What happens next is up to you.
185+
186+### Check suites
187+
188+Each reporter's check runs on a commit form one check suite, with a
189+status and conclusion worked out from its latest runs: in progress while any
190+is, then the worst conclusion. A workflow run is the suite of its jobs.
191+[`list_check_suites_for_ref`](/reference/api/checks/list-check-suites-for-ref/)
192+lists a commit's suites. A suite completing sends `check_suite.completed`.
193+
194+## From a workflow
195+
196+A workflow job reports extra check runs with its own `G1T_TOKEN`. They
197+report as **g1t Actions**:
198+
199+```yaml
200+- name: Report coverage
201+ if: always()
202+ env:
203+ G1T_TOKEN: ${{ secrets.G1T_TOKEN }}
204+ run: |
205+ curl -sf -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/check-runs" \
206+ -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
207+ -d "{\"name\": \"coverage\", \"head_sha\": \"$GITHUB_SHA\", \"conclusion\": \"neutral\", \"output\": {\"title\": \"81% covered\", \"summary\": \"Up 2% from main.\"}}"
208+```
209+
210+A pull request's runs from someone without the Write role get no token
211+that can write, so they cannot report checks.
212+
213+## Required checks
214+
215+A [required status check](/guides/pull-requests/#required-status-checks),
216+in branch protection or a [ruleset](/guides/rules/), is met by a status of
217+its name or a check run of its name alike:
218+
219+| What reported it | Counts as |
220+| --- | --- |
221+| A status `success` | Passing |
222+| A status `failure` or `error` | Failing |
223+| A status `pending` | Running: the merge waits |
224+| A check run not yet completed | Running: the merge waits |
225+| A check run completed `success`, `neutral` or `skipped` | Passing |
226+| A check run completed `failure`, `cancelled`, `timed_out` or `action_required` | Failing |
227+| Nothing yet | Expected: the merge waits |
228+
229+A workflow is required by its name, such as `CI`, which all its jobs
230+report under. To require only what was reported through the API, pin the
231+check to the `api` integration in a ruleset:
232+`{"context": "lint", "integration": "api"}`.
233+
234+A pull request [g1t is working on](/guides/working-with-g1t/#seeing-it-through)
235+goes back to g1t when a check fails, whatever reported it.
236+
237+## Run again
238+
239+[`rerequest_check_run`](/reference/api/checks/rerequest-check-run/) and
240+[`rerequest_check_suite`](/reference/api/checks/rerequest-check-suite/), or
241+**Re-run** on a check run's page, ask for it to run again. A check run
242+reported through the API sends its reporter `check_run.rerequested` (or
243+`check_suite.rerequested`): run it again and report a new check run. A
244+workflow job's run runs again, which also needs `workflows:write`.
245+
246+## Webhooks
247+
248+[Webhooks](/guides/webhooks/) can be sent:
249+
250+| Event | When |
251+| --- | --- |
252+| `status.created` | A status was set on a commit through the API. |
253+| `check_run.created` | A check run was reported. |
254+| `check_run.completed` | A check run completed. |
255+| `check_run.rerequested` | Someone asked for a check run to run again. |
256+| `check_run.requested_action` | Someone pressed one of a check run's buttons. |
257+| `check_suite.completed` | Every latest check run of a suite completed. |
258+| `check_suite.rerequested` | Someone asked for a check suite to run again. |
259+
260+These are left out of a repository's timeline.
261+
262+## Who can report checks
263+
264+| | Can |
265+| --- | --- |
266+| Anyone who can read the repository | See its checks, and read them through the API with `checks:read` (no token for a public repository) |
267+| The Write role and up, with `checks:write` | Report statuses and check runs, and ask for them to run again |
268+| A workflow job, with `G1T_TOKEN` | The same, in its repository |
269+| g1t's agents | Read checks, never report them |
270+
271+An agent's own work is never judged by checks it reported: what a check
272+says is up to your CI and integrations.
273+
274+## From the API and MCP
275+
276+| Route | Operation |
277+| --- | --- |
278+| `POST /repos/{owner}/{name}/statuses/{sha}` | [`create_commit_status`](/reference/api/checks/create-commit-status/) |
279+| `GET /repos/{owner}/{name}/commits/{ref}/statuses` | [`list_commit_statuses`](/reference/api/checks/list-commit-statuses/) |
280+| `GET /repos/{owner}/{name}/commits/{ref}/status` | [`get_combined_status`](/reference/api/checks/get-combined-status/) |
281+| `POST /repos/{owner}/{name}/check-runs` | [`create_check_run`](/reference/api/checks/create-check-run/) |
282+| `PATCH /repos/{owner}/{name}/check-runs/{id}` | [`update_check_run`](/reference/api/checks/update-check-run/) |
283+| `GET /repos/{owner}/{name}/check-runs/{id}` | [`get_check_run`](/reference/api/checks/get-check-run/) |
284+| `GET /repos/{owner}/{name}/check-runs/{id}/annotations` | [`list_check_run_annotations`](/reference/api/checks/list-check-run-annotations/) |
285+| `POST /repos/{owner}/{name}/check-runs/{id}/rerequest` | [`rerequest_check_run`](/reference/api/checks/rerequest-check-run/) |
286+| `GET /repos/{owner}/{name}/commits/{ref}/check-runs` | [`list_check_runs_for_ref`](/reference/api/checks/list-check-runs-for-ref/) |
287+| `GET /repos/{owner}/{name}/commits/{ref}/check-suites` | [`list_check_suites_for_ref`](/reference/api/checks/list-check-suites-for-ref/) |
288+| `GET /repos/{owner}/{name}/check-suites/{id}` | [`get_check_suite`](/reference/api/checks/get-check-suite/) |
289+| `POST /repos/{owner}/{name}/check-suites/{id}/rerequest` | [`rerequest_check_suite`](/reference/api/checks/rerequest-check-suite/) |
290+
291+[`list_check_runs_for_ref`](/reference/api/checks/list-check-runs-for-ref/)
292+gives each name's latest run and each workflow's latest run per event;
293+`filter=all` gives every one. Narrow it with `check_name`, `status` and
294+`app` (a reporter's slug; `actions` for workflow jobs).
295+
296+On the [MCP server](/reference/mcp/#workflow), the `workflow` tool has an
297+action for each: `combined_status`, `list_statuses`, `set_status`,
298+`list_check_runs`, `get_check_run`, `check_run_annotations`,
299+`create_check_run`, `update_check_run`, `rerequest_check_run`,
300+`list_check_suites`, `get_check_suite` and `rerequest_check_suite`.
+3−1
2222 workflow. A workflow named `CI` reports the check `CI`; its status context
2323 is `CI / pull_request`, the workflow's name and the event it ran for. Other
2424 parts of g1t report under their own names, such as `g1t / deploy` (or
25−`g1t / deploy (<project>)`) for a [deployment](/guides/deployments/).
25+`g1t / deploy (<project>)`) for a [deployment](/guides/deployments/), and
26+your own CI and integrations report statuses and check runs through the
27+API: see [Checks](/guides/checks/).
2628
2729 Which checks a merge needs is up to the repository: its
2830 [required status checks](#required-status-checks). The merge box lists
+1−1
157157
158158 | Parameter | Default | What it does |
159159 | --- | --- | --- |
160−| `checks` | None | Each check has a `context`, such as `CI` (a workflow's name) or `g1t / deploy`, and an optional `integration`: `actions`, `deployments`, `security` or `g1t`. A check with an `integration` counts only when that integration reported it, so a workflow cannot stand in for a deployment. |
160+| `checks` | None | Each check has a `context`, such as `CI` (a workflow's name) or `g1t / deploy`, and an optional `integration`: `actions`, `deployments`, `security`, `g1t` or `api` (a status or [check run](/guides/checks/) reported through the API). A check with an `integration` counts only when that integration reported it, so a workflow cannot stand in for a deployment. A check is met by a status or a check run of its name alike. |
161161 | `strict` | `false` | The pull request must contain the branch's latest commits, so what merges is exactly what was checked. |
162162 | `paths` | Always | The checks are required only when the pull request changes a file matching one of these patterns. |
163163 | `allow_bypass_on_merge` | `false` | Someone who may merge can merge past checks that have not passed by ticking **Bypass the required checks**. |
+4−0
9292 | `code_scanning_alert.created`, `.fixed`, `.dismissed`, `.reopened` | A [code scanning alert](/guides/security/code-scanning/) changed. `data.alert_id`, `data.alert_number`, `data.title`, `data.severity`, `data.path`, `data.line`, `data.state`, `data.link`. |
9393 | `vulnerability_alert.created`, `.fixed`, `.dismissed`, `.reopened` | A [vulnerability alert](/guides/security/supply-chain/) changed. `data.alert_id`, `data.title` (package, version, lockfile and advisory), `data.severity`, `data.state`, `data.link`. |
9494 | `checks.completed` | A pull request's checks finished: every status on its head has reported and none is still pending, or the merge queue took it out. `data.number`, `data.commit`, and `data.status`, `passed` or `failed`. |
95+| `status.created` | A status was set on a commit through the API. `data.sha`, `data.context`, `data.state`, `data.description`, `data.target_url`. See [Checks](/guides/checks/). |
96+| `check_run.created`, `check_run.completed` | A check run was reported on a commit, or completed. `data.check_run`: `id`, `name`, `head_sha`, `status`, `conclusion`, `details_url`, `external_id`, `html_url`, `output`, `actions`, `check_suite` and `app`. |
97+| `check_run.rerequested`, `check_run.requested_action` | Someone asked for a check run to run again, or pressed one of its buttons: `data.requested_action` is the button's `identifier`. Report a new run, or do what the button says. |
98+| `check_suite.completed`, `check_suite.rerequested` | Every latest check run of a reporter's suite on a commit completed, or someone asked for it to run again. `data.check_suite`: `id`, `head_sha`, `status`, `conclusion`, `app`. |
9599 | `review.completed` | g1t reviewed a pull request. `data.verdict`. |
96100 | `workflow.completed` | A [workflow](/guides/actions/) run finished. `data.workflow`, `data.conclusion`, `data.run_id`, `data.sha`, `data.pull`. |
97101 | `deployment.succeeded`, `deployment.failed` | A g1t.page build of a [project](/guides/deployments/) finished, for production or a pull request's preview. `data.deployment_id`, `data.project`, `data.kind` (`production` or `preview`), `data.number` for a preview, `data.commit`, `data.path`, `data.error` on failure, and `data.recovered` when a success follows a failure. |
+4−2
181181
182182 1. **Checks.** Every push the agent makes runs the repository's
183183 [workflows](/guides/actions/) on the pull request, as for anyone's pull
184− request. g1t waits for them to finish. Only the workflow runs report, so
185− the agent has no say in the result.
184+ request. g1t waits for them to finish. Only the workflow runs and your
185+ integrations report [checks](/guides/checks/), so the agent has no say
186+ in the result: it reads them, with each failing check run's annotations,
187+ and never reports one.
186188 2. **Review.** A different agent reads the change and posts comments on
187189 lines, a summary and a verdict.
188190 3. **Revision.** If a check fails or the review asks for changes, the
+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

This change is too large to show in full.