markdown · 4527 bytes Raw Blame History

Status checks

External CI systems publish status into shithub via the check-runs API. Branch-protection rules can require named contexts to pass before a PR merges.

Concepts

  • A check run is one invocation: (repo, head_sha, name) with a state and metadata.
  • A check suite is the bag of check runs for one head SHA; shithub computes it server-side.
  • Branch protection's "required status checks" matches by name on the head commit.

Create a check run

POST /api/v1/repos/{owner}/{repo}/check-runs

Required scope: repo (write).

Request body

{
  "name":       "ci/lint",
  "head_sha":   "8c4e3f2a1b…",
  "status":     "in_progress",
  "started_at": "2026-05-09T16:00:00Z",
  "details_url": "https://ci.example/runs/12345",
  "external_id": "12345",
  "output": {
    "title":   "Lint",
    "summary": "Running golangci-lint",
    "text":    "..."
  }
}
Field Required Notes
name yes The context name. Match this on branch-protection rules.
head_sha yes 40-char SHA-1 of the commit being checked.
status no queued, in_progress, completed. Default queued.
conclusion iff status=completed success, failure, neutral, cancelled, timed_out, action_required.
started_at no RFC 3339.
completed_at iff status=completed RFC 3339.
details_url no Where humans go to inspect the run.
external_id no Your system's identifier; echoed back on read.
output no {title, summary, text}. Summary is markdown.

Response

201 Created with the full check-run resource:

{
  "id":         9876,
  "name":       "ci/lint",
  "head_sha":   "8c4e3f2a1b…",
  "status":     "in_progress",
  "conclusion": null,
  "started_at": "2026-05-09T16:00:00Z",
  "details_url": "https://ci.example/runs/12345",
  "external_id": "12345",
  "output":     { "title": "Lint", "summary": "Running golangci-lint" }
}

Update a check run

PATCH /api/v1/repos/{owner}/{repo}/check-runs/{id}

Required scope: repo (write).

Same body shape as create; partial. Typical use: flip status: "completed" with a conclusion.

Conclusion semantics

Conclusion Effect on PR merge gate
success Counts as passing.
failure Blocks merge.
neutral Doesn't block; doesn't count as passing either.
cancelled Blocks merge.
timed_out Blocks merge.
action_required Blocks merge with a human-action signal.

List check runs for a commit

GET /api/v1/repos/{owner}/{repo}/commits/{sha}/check-runs

Required scope: repo:read.

Returns the most recent check run per name on this commit. Older runs are still in the database (audit) but not in this listing.

{
  "total_count": 2,
  "check_runs": [
    {"id": 9876, "name": "ci/lint",  "status": "completed", "conclusion": "success", ...},
    {"id": 9877, "name": "ci/test",  "status": "in_progress", ...}
  ]
}

List check suites for a commit

GET /api/v1/repos/{owner}/{repo}/commits/{sha}/check-suites

Required scope: repo:read.

Returns the rolled-up suite view per head_sha:

{
  "total_count": 1,
  "check_suites": [
    {
      "head_sha":  "8c4e3f2a1b…",
      "status":    "in_progress",
      "conclusion": null,
      "checks_url": "/api/v1/repos/owner/repo/commits/8c4e3f2a1b…/check-runs"
    }
  ]
}

Idempotency

Check-run creates with the same (repo, head_sha, name, external_id) are coalesced — re-publishing the same run from a retried CI job is safe.

Permissions

The PAT must belong to a user with write access to the repo. Bot accounts representing CI runners are the typical pattern; they're regular shithub accounts with PATs scoped to repo and nothing else.

View source
1 # Status checks
2
3 External CI systems publish status into shithub via the check-runs
4 API. Branch-protection rules can require named contexts to pass
5 before a PR merges.
6
7 ## Concepts
8
9 - A **check run** is one invocation: `(repo, head_sha, name)`
10 with a state and metadata.
11 - A **check suite** is the bag of check runs for one head SHA;
12 shithub computes it server-side.
13 - Branch protection's "required status checks" matches by **name**
14 on the head commit.
15
16 ## Create a check run
17
18 ```
19 POST /api/v1/repos/{owner}/{repo}/check-runs
20 ```
21
22 Required scope: `repo` (write).
23
24 ### Request body
25
26 ```json
27 {
28 "name": "ci/lint",
29 "head_sha": "8c4e3f2a1b…",
30 "status": "in_progress",
31 "started_at": "2026-05-09T16:00:00Z",
32 "details_url": "https://ci.example/runs/12345",
33 "external_id": "12345",
34 "output": {
35 "title": "Lint",
36 "summary": "Running golangci-lint",
37 "text": "..."
38 }
39 }
40 ```
41
42 | Field | Required | Notes |
43 |----------------|----------|------------------------------------------------------------|
44 | `name` | yes | The context name. Match this on branch-protection rules. |
45 | `head_sha` | yes | 40-char SHA-1 of the commit being checked. |
46 | `status` | no | `queued`, `in_progress`, `completed`. Default `queued`. |
47 | `conclusion` | iff `status=completed` | `success`, `failure`, `neutral`, `cancelled`, `timed_out`, `action_required`. |
48 | `started_at` | no | RFC 3339. |
49 | `completed_at` | iff `status=completed` | RFC 3339. |
50 | `details_url` | no | Where humans go to inspect the run. |
51 | `external_id` | no | Your system's identifier; echoed back on read. |
52 | `output` | no | `{title, summary, text}`. Summary is markdown. |
53
54 ### Response
55
56 `201 Created` with the full check-run resource:
57
58 ```json
59 {
60 "id": 9876,
61 "name": "ci/lint",
62 "head_sha": "8c4e3f2a1b…",
63 "status": "in_progress",
64 "conclusion": null,
65 "started_at": "2026-05-09T16:00:00Z",
66 "details_url": "https://ci.example/runs/12345",
67 "external_id": "12345",
68 "output": { "title": "Lint", "summary": "Running golangci-lint" }
69 }
70 ```
71
72 ## Update a check run
73
74 ```
75 PATCH /api/v1/repos/{owner}/{repo}/check-runs/{id}
76 ```
77
78 Required scope: `repo` (write).
79
80 Same body shape as create; partial. Typical use: flip
81 `status: "completed"` with a `conclusion`.
82
83 ### Conclusion semantics
84
85 | Conclusion | Effect on PR merge gate |
86 |-------------------|--------------------------------------------------------|
87 | `success` | Counts as passing. |
88 | `failure` | Blocks merge. |
89 | `neutral` | Doesn't block; doesn't count as passing either. |
90 | `cancelled` | Blocks merge. |
91 | `timed_out` | Blocks merge. |
92 | `action_required` | Blocks merge with a human-action signal. |
93
94 ## List check runs for a commit
95
96 ```
97 GET /api/v1/repos/{owner}/{repo}/commits/{sha}/check-runs
98 ```
99
100 Required scope: `repo:read`.
101
102 Returns the most recent check run **per name** on this commit.
103 Older runs are still in the database (audit) but not in this
104 listing.
105
106 ```json
107 {
108 "total_count": 2,
109 "check_runs": [
110 {"id": 9876, "name": "ci/lint", "status": "completed", "conclusion": "success", ...},
111 {"id": 9877, "name": "ci/test", "status": "in_progress", ...}
112 ]
113 }
114 ```
115
116 ## List check suites for a commit
117
118 ```
119 GET /api/v1/repos/{owner}/{repo}/commits/{sha}/check-suites
120 ```
121
122 Required scope: `repo:read`.
123
124 Returns the rolled-up suite view per `head_sha`:
125
126 ```json
127 {
128 "total_count": 1,
129 "check_suites": [
130 {
131 "head_sha": "8c4e3f2a1b…",
132 "status": "in_progress",
133 "conclusion": null,
134 "checks_url": "/api/v1/repos/owner/repo/commits/8c4e3f2a1b…/check-runs"
135 }
136 ]
137 }
138 ```
139
140 ## Idempotency
141
142 Check-run creates with the same `(repo, head_sha, name,
143 external_id)` are coalesced — re-publishing the same run from a
144 retried CI job is safe.
145
146 ## Permissions
147
148 The PAT must belong to a user with **write access** to the repo.
149 Bot accounts representing CI runners are the typical pattern;
150 they're regular shithub accounts with PATs scoped to `repo` and
151 nothing else.