v0.1.0 4db3b8b

runner.proto RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3syntax = "proto3";
4
5package ermite;
6
7import "google/protobuf/duration.proto";
8import "google/protobuf/timestamp.proto";
9
10// CiStatus is where a run, a job or a check is in its life. It is the same
11// as GitHub's status, without the rarely used values.
12enum CiStatus {
13  // Not set. In a filter it means every status. Never returned by the server.
14  CI_STATUS_UNSPECIFIED = 0;
15  // Waiting: for a runner, for the jobs it needs, or for its turn
16  // (max-parallel).
17  CI_STATUS_QUEUED = 1;
18  // At least one job has been given to a runner and has not ended.
19  CI_STATUS_IN_PROGRESS = 2;
20  // Over. The conclusion says how it ended.
21  CI_STATUS_COMPLETED = 3;
22}
23
24// CiConclusion is how a run, a job, a step or a check ended, with GitHub's
25// values and meaning. It is unset (CI_CONCLUSION_UNSPECIFIED) until the status is
26// COMPLETED.
27enum CiConclusion {
28  // Not completed yet. Never returned by the server for a completed item.
29  CI_CONCLUSION_UNSPECIFIED = 0;
30  // Everything it ran succeeded.
31  CI_CONCLUSION_SUCCESS = 1;
32  // A step failed, or the job failed for another reason described in
33  // RunJob.error (runner lost, queue timeout, log limit, feature not
34  // supported yet, invalid workflow file).
35  CI_CONCLUSION_FAILURE = 2;
36  // Cancelled by the owner, or by fail-fast, or because the runner shut down.
37  CI_CONCLUSION_CANCELLED = 3;
38  // Never ran: its "if" was false, or a job it needs did not succeed, or it was
39  // not selected. Never reported by a runner.
40  CI_CONCLUSION_SKIPPED = 4;
41  // Exceeded its timeout.
42  CI_CONCLUSION_TIMED_OUT = 5;
43  // Neither a success nor a failure. Defined for parity with GitHub; no
44  // workflow job ends this way yet. For merging it counts as a success.
45  CI_CONCLUSION_NEUTRAL = 6;
46  // Waits for a human action (for example the approval of an environment).
47  // Defined for parity with GitHub; the server does not produce it before
48  // environments exist. For merging it counts as a failure.
49  CI_CONCLUSION_ACTION_REQUIRED = 7;
50}
51
52// StepState is where a step is in its life.
53enum StepState {
54  // Not set. Rejected in requests, never returned by the server.
55  STEP_STATE_UNSPECIFIED = 0;
56  // Known but not started.
57  STEP_STATE_PENDING = 1;
58  // Running.
59  STEP_STATE_IN_PROGRESS = 2;
60  // Over. The conclusion says how it ended. A step that did not run because
61  // of its "if" or an earlier failure is COMPLETED with CI_CONCLUSION_SKIPPED.
62  STEP_STATE_COMPLETED = 3;
63}
64
65// RunnerIsolation is how a runner isolates each job from the host and from
66// other jobs. The runner chooses it (auto-detection included) and reports it
67// at registration; the job page shows it.
68enum RunnerIsolation {
69  // Not set. Rejected at registration, never returned by the server.
70  RUNNER_ISOLATION_UNSPECIFIED = 0;
71  // One micro-VM per job, destroyed at the end.
72  RUNNER_ISOLATION_VM = 1;
73  // One container per job with its own Docker daemon, run by the sysbox
74  // runtime. Shares the host kernel.
75  RUNNER_ISOLATION_CONTAINER_SYSBOX = 2;
76  // One privileged container per job with its own Docker daemon. Shares the
77  // host kernel and is equivalent to root on the host: the weakest mode.
78  RUNNER_ISOLATION_CONTAINER_PRIVILEGED = 3;
79}
80
81// CancelReason says why the server asks a runner to stop a job.
82enum CancelReason {
83  // Not set. Never sent in a CancelRequest.
84  CANCEL_REASON_UNSPECIFIED = 0;
85  // The owner cancelled the run (RunService.CancelRun, web or CLI). The job
86  // ends CI_CONCLUSION_CANCELLED.
87  CANCEL_REASON_USER = 1;
88  // Another job of the same matrix failed and the strategy has fail-fast. The
89  // job ends CI_CONCLUSION_CANCELLED.
90  CANCEL_REASON_FAIL_FAST = 2;
91  // The job exceeded its time budget (see Job.timeout_minutes). The job ends
92  // CI_CONCLUSION_TIMED_OUT.
93  CANCEL_REASON_TIMEOUT = 3;
94}
95
96// RunnerService is what a CI runner (ermite-runner) calls to get jobs and
97// report on them. It is not for people: owners use RunService.
98//
99// Three credentials are used, each by different RPCs. All are sent as
100// "Authorization: Bearer <secret>":
101//
102//   - RegisterRunner: a stored token with the runner:register scope, or the
103//     root token.
104//   - FetchJob: the runner token returned by RegisterRunner. It identifies the
105//     runner and only works for FetchJob. It is "ermiterun_" followed by 43
106//     base64url characters (32 random bytes, no padding).
107//   - UpdateJob, AppendLog, Heartbeat, WatchJob, CompleteJob: the job token of
108//     the Job that FetchJob returned. It is valid for that one job and that
109//     one assignment. It is "ermitejob_" followed by 43 base64url characters
110//     (32 random bytes, no padding). The job_id of the request must be the
111//     one of the token.
112//
113// Errors. unauthenticated means only that the credential is bad: missing,
114// unknown, expired, revoked, of the wrong kind for the RPC, or a job token
115// that was replaced (the job was given again, see FetchJob) or is older than
116// the 24 hours below. A runner that gets it for a job abandons the job at
117// once. A job token that is valid but belongs to another job than the job_id
118// of the request gets permission_denied. Repeated failed authentications are
119// rate limited per client address.
120//
121// The end of a job. A job ends when its conclusion is set, by CompleteJob or
122// by the server (cancellation not honoured, lost runner, log limit, queue
123// timeout). From then on UpdateJob, AppendLog, Heartbeat and WatchJob answer
124// failed_precondition with a JobEnded error detail (a JobEnded message in the
125// Connect error details) that gives the conclusion; the runner stops the job
126// and sends nothing more. The job token is still recognised for 24 hours after
127// the end, so that these calls can say so and so that CompleteJob, the only
128// call that still does something, returns the stored result to a runner that
129// lost the answer of its last call or that was cut off by the server. After
130// the 24 hours every call gets unauthenticated.
131//
132// Secrets and trust. Runner tokens and job tokens are secrets: the server
133// never logs them and clients must not log them. The job token stays in the
134// runner host process; it is never given to the sandbox that runs the job's
135// code. A runner receives the jobs of every repository, with their workflow
136// files, contexts and (from CI-2) their code, and from CI-3 their secrets: the
137// runner:register scope therefore effectively gives read access to the code
138// and the secrets of every repository, private ones included. Give it only to
139// runners you operate.
140//
141// Servers that implement this service list "ci.runners" in
142// GetVersionResponse.features.
143service RunnerService {
144  // RegisterRunner registers a runner and returns its runner token, which is
145  // shown only in this response; the runner keeps it. The caller needs the
146  // runner:register scope on a token that is not restricted to repositories
147  // (a runner serves every repository), or the root token. Fails with
148  // unauthenticated without a valid token, permission_denied without the
149  // scope or with a repository-restricted token, invalid_argument if a field
150  // breaks a rule, and already_exists if name is taken by another runner and
151  // replace is not set.
152  rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse);
153  // FetchJob asks for a job to run (long poll). The server holds the call
154  // until a queued job matches the runner, or about 50 seconds (never more
155  // than 50) pass, then answers; without a job the response has no job and the
156  // runner calls again after retry_after. Clients set a call timeout above 50
157  // seconds (60 is suggested) and must not poll when they have no free
158  // capacity.
159  //
160  // A job matches when every label of its runs-on is among the labels of the
161  // runner (case-insensitive). A job is given to exactly one runner, even when
162  // many runners and many polls compete; the assignment is recorded before
163  // the response is sent. The job stays QUEUED for the owner until it is
164  // acknowledged.
165  //
166  // Acknowledgement. The runner's first Heartbeat for the job is the
167  // acknowledgement, and the runner sends it as soon as it has the job, before
168  // anything else. If none arrives within 30 seconds of the assignment (the
169  // response was lost, or the runner died), the server cancels the assignment:
170  // the job token is revoked, the job goes back to QUEUED with a fresh token
171  // and can be given to any runner, the same one included; nothing the old
172  // assignment stored is kept. This is not a failure of the job. A job whose
173  // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE
174  // and an error saying so.
175  //
176  // Retry after a lost response. poll_id lets a client retry a poll safely:
177  // a call with the same poll_id as an earlier call of the same runner that
178  // assigned a job returns that same assignment (same Job, same token) as long
179  // as it has not been acknowledged or cancelled, instead of taking another.
180  //
181  // A runner that crashed or whose host restarted cannot resume a job: it does
182  // not keep the job token, so the job fails with the lost-runner error after
183  // the heartbeat timeout (see Heartbeat).
184  //
185  // Needs the runner token. Fails with unauthenticated when the runner was
186  // removed (also ending a call in progress) and invalid_argument if labels
187  // or poll_id break a rule.
188  rpc FetchJob(FetchJobRequest) returns (FetchJobResponse);
189  // UpdateJob reports the state of the steps of a job while it runs. Needs the
190  // job token. Each call is applied whole or not at all, and repeating an
191  // identical call changes nothing, so a runner may retry after a network
192  // error. To end the job use CompleteJob.
193  //
194  // Fails with invalid_argument if a rule is broken, with failed_precondition
195  // if a step goes back (a COMPLETED step cannot change, a step cannot go from
196  // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail
197  // if the job has ended.
198  rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse);
199  // AppendLog appends lines to the log of a job. Needs the job token.
200  //
201  // The log of a job is one sequence of lines, numbered from 0 across all
202  // steps; offset is the number of lines the server holds when the request is
203  // applied, that is the number of the first line of the request. Calls must
204  // go in order, one at a time, and are idempotent:
205  //
206  //   - offset equal to the server's line count: the lines are appended;
207  //   - offset below it: the first (count - offset) lines were already stored
208  //     (a retry after a lost response) and are skipped, and the rest is
209  //     appended; a request entirely stored changes nothing. The skipped lines
210  //     are not compared with the stored ones: a retry whose lines differ from
211  //     the first attempt is silently dropped, so a client must resend the
212  //     same lines at the same offset;
213  //   - offset above it: out_of_range (a gap is a bug of the client; the
214  //     message gives the line count the server holds).
215  //
216  // The response gives the line count after the call, so a client can check
217  // itself. Offsets resume only after a network error within the same runner
218  // process; they are not a way to resume a job after a crash.
219  //
220  // A request has at most 1,000 lines whose text adds up to at most 64 KiB
221  // (65,536 bytes), all in the same step; more is invalid_argument. A line
222  // longer than that is cut by the client into pieces, see LogLine.continued.
223  //
224  // Steps. The step of a request must be at least the step of the last line
225  // the server holds, because the log is ordered by step (applied lines only:
226  // lines skipped as already stored are not checked); a lower step is
227  // failed_precondition. Lines for a step that UpdateJob has not announced
228  // create it: it starts IN_PROGRESS at the time of its first line, with an
229  // empty name until an UpdateJob gives one.
230  //
231  // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
232  // the server (text, timestamps and separators). The call that would exceed
233  // it appends nothing; the server adds a last line saying that the limit was
234  // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and
235  // answers failed_precondition with a JobEnded detail like any call after the
236  // end of a job. The runner then stops the job.
237  //
238  // Lines sent after the end of the job are rejected, so a runner flushes all
239  // its AppendLog lines before it calls CompleteJob.
240  //
241  // Fails with invalid_argument if a rule is broken (step number, text with a
242  // line feed or invalid UTF-8, missing time, a continued first line).
243  rpc AppendLog(AppendLogRequest) returns (AppendLogResponse);
244  // Heartbeat tells the server that the runner is alive and still working on
245  // the job. Needs the job token. The first one acknowledges the assignment
246  // (see FetchJob). The runner then calls it every 30 seconds for as long as
247  // the job runs. The deadline is 2 minutes after the later of the last
248  // heartbeat and the start of the server (so a server restart does not fail
249  // the jobs of runners that could not reach it): past it the server ends the
250  // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner:
251  // <name> lost communication with the server...". Only Heartbeat counts:
252  // other calls do not keep a job alive.
253  //
254  // A runner that gets unavailable (or any network error) retries Heartbeat
255  // every 5 seconds until it succeeds, instead of waiting for its next 30
256  // second tick, and keeps the job running meanwhile.
257  rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
258  // WatchJob streams what the runner must react to. Needs the job token. The
259  // runner opens it when it starts the job and keeps it open; it reopens it
260  // after any error other than unauthenticated and the failed_precondition
261  // that says the job ended, and after a clean close of
262  // the stream that did not end with a JobEnded message.
263  //
264  // When the stream opens and a cancellation has already been requested, the
265  // first message is that request, so a reconnect never misses one. Only the
266  // first reason is sent: a job already cancelled by the owner does not get a
267  // second message when its time budget expires. The server also sends a
268  // Keepalive at least every 30 seconds, which the client ignores. When the
269  // job ends the server sends JobEnded, then closes the stream without error.
270  // On a job that has already ended the call fails like the other RPCs
271  // (failed_precondition with a JobEnded detail).
272  //
273  // On a CancelRequest the runner stops the job as the official runner does
274  // (interrupt, then terminate, then kill) and reports the conclusion given
275  // for the reason with CompleteJob.
276  //
277  // The server does not trust the runner to obey. If no conclusion arrives
278  // within 60 seconds after a cancellation request, whatever the reason (user,
279  // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED
280  // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error
281  // saying that the runner did not answer, and the steps not completed are
282  // completed as cancelled. The runner's later CompleteJob returns that result.
283  rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse);
284  // CompleteJob ends a job: it reports the final steps, the outputs and the
285  // conclusion. Needs the job token, which stays valid for this RPC for 24
286  // hours after the end of the job, however it ended. The call is applied whole
287  // or not at all. Once the job has ended, the token works for no other RPC
288  // (they answer failed_precondition with a JobEnded detail).
289  //
290  // It is idempotent. The first call that finds the job running ends it and
291  // answers applied = true. Any later call, a retry after a lost response or a
292  // call after the server ended the job itself, is not validated and changes
293  // nothing: it answers OK with the stored conclusion and error and
294  // applied = false (a runner whose conclusion differs from the stored one
295  // simply logs it; the outputs it sent are dropped).
296  //
297  // Lines sent with AppendLog after this call are rejected: a runner flushes
298  // all its log lines first. Steps that were not reported COMPLETED when the job
299  // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
300  // they never started and the job did not end by cancellation).
301  //
302  // Fails with invalid_argument if a rule is broken (including the output
303  // limits) and failed_precondition if a step goes back, as in UpdateJob.
304  rpc CompleteJob(CompleteJobRequest) returns (CompleteJobResponse);
305}
306
307// RegisterRunnerRequest describes the runner to register.
308message RegisterRunnerRequest {
309  // Name identifies the runner for people (job page, logs, ListRunners) and is
310  // unique: 1 to 64 characters among letters, digits, ".", "_" and "-". It is
311  // not a label and never matches runs-on.
312  string name = 1;
313  // Labels are the labels the runner adds to its implicit ones: at most 64,
314  // each 1 to 64 characters among letters, digits, ".", "_" and "-", for
315  // example "ubuntu-latest". Comparison is case-insensitive and duplicates
316  // are removed. The server adds the implicit labels "self-hosted", the os
317  // and the arch ("linux", "x64" or "arm64"); the runner name is not a label.
318  repeated string labels = 2;
319  // Version is the release of the runner, for example "v0.1.0", or "devel".
320  // Informational, at most 64 characters.
321  string version = 3;
322  // Isolation is how the runner isolates jobs. Required.
323  RunnerIsolation isolation = 4;
324  // Arch is the CPU architecture, in GitHub's names: "x64" or "arm64". Other
325  // values are invalid_argument (the set may grow with compatible additions).
326  string arch = 5;
327  // Os is the operating system: "linux". Other values are invalid_argument
328  // (the set may grow with compatible additions).
329  string os = 6;
330  // Replace removes the runner that already has this name, if any, before
331  // registering this one: its runner token is revoked at once, as in
332  // RunService.RemoveRunner, and a job it is running is not affected. The new
333  // runner gets a new runner_id. Needs the same scope as registering. Without
334  // replace, a name in use is already_exists. It is how a runner re-registers
335  // after losing its state.
336  bool replace = 7;
337}
338
339// RegisterRunnerResponse holds the new runner and its secret.
340message RegisterRunnerResponse {
341  // RunnerId identifies the runner in ListRunners and RemoveRunner. It is not
342  // a secret.
343  string runner_id = 1;
344  // RunnerToken is the secret of the runner, to present as "Authorization:
345  // Bearer <runner_token>" to FetchJob: "ermiterun_" and 43 base64url
346  // characters (32 random bytes, no padding). It is returned once: the server
347  // keeps only its SHA-256, the runner must store it (mode 0600). It stops
348  // working when the runner is removed or replaced.
349  string runner_token = 2;
350}
351
352// FetchJobRequest asks for a job.
353message FetchJobRequest {
354  // Labels narrows the labels offered for this call. Empty means all the
355  // labels of the runner, which is the normal case. Otherwise each one must be
356  // a label of the runner (invalid_argument if not) and only jobs whose runs-on
357  // labels are all among them are offered.
358  repeated string labels = 1;
359  // PollId identifies this poll, chosen by the client (for example 16 random
360  // bytes in base64url), 1 to 64 characters among letters, digits, "_" and
361  // "-". A client that retries a call whose response it did not get sends the
362  // same poll_id, and gets the assignment the first call made, if it made one
363  // (see FetchJob). A new poll uses a new id. Empty means no retry protection:
364  // a lost assignment is then recovered by the acknowledgement timeout alone.
365  string poll_id = 2;
366}
367
368// FetchJobResponse holds the job given to the runner, if any.
369message FetchJobResponse {
370  // Job is the job to run. Unset when no job was available before the end of
371  // the poll.
372  Job job = 1;
373  // RetryAfter is how long the client waits before its next FetchJob. Unset or
374  // zero means call again at once. The server sets it when it has no job for
375  // the runner and wants to slow polling down (load, shutdown in progress);
376  // clients obey it and may wait longer, never less. Unset when job is set.
377  google.protobuf.Duration retry_after = 2;
378}
379
380// Job is a job given to a runner: everything needed to run it, nothing more.
381// The runner parses workflow_yaml itself; the protocol does not carry the
382// workflow model, so it does not change when the model grows.
383message Job {
384  // JobId identifies the job on the server. It is opaque, unique across
385  // repositories and runs, and the same for every attempt of the job. With
386  // attempt it names one execution.
387  string job_id = 1;
388  // RunId is the run the job belongs to, the "github.run_id". It is global to
389  // the server.
390  int64 run_id = 2;
391  // Attempt is the attempt of the run this job executes: 1 for the first, and
392  // one more after each RunService.RerunRun. It is the "github.run_attempt".
393  int32 attempt = 3;
394  // JobToken is the secret that authenticates this job's UpdateJob,
395  // AppendLog, Heartbeat, WatchJob and CompleteJob calls ("Authorization:
396  // Bearer <job_token>"): "ermitejob_" and 43 base64url characters (32 random
397  // bytes, no padding). It is random, valid from the assignment until the job
398  // ends (and for CompleteJob 24 hours more), survives a server restart and
399  // is shown only in this response, or in a repeat of the same poll_id. It
400  // must stay in the runner host process and never reach the job's code. A new
401  // assignment of the same job has a new token.
402  string job_token = 4;
403  // WorkflowPath is the path of the workflow file in the repository, for
404  // example ".github/workflows/ci.yml".
405  string workflow_path = 5;
406  // WorkflowYaml is the content of that file at sha, as is, at most 1 MiB
407  // (larger files fail at planning).
408  string workflow_yaml = 6;
409  // JobKey is the key of the job under "jobs:" in the workflow, the
410  // "github.job".
411  string job_key = 7;
412  // Matrix is the JSON object of the matrix values of this instance of the
413  // job, for example {"os":"linux","go":"1.27"}. "{}" when the job has no
414  // matrix. It is the "matrix" context.
415  string matrix = 8;
416  // Contexts is a JSON object with the contexts that the server computed for
417  // this job: "github", "needs", "strategy", "matrix", "inputs" and "vars", in
418  // the shape GitHub gives them. The contexts "job", "runner", "steps" and
419  // "env" belong to the runner and are not in it. Other keys may be added
420  // later; clients ignore those they do not know.
421  string contexts = 9;
422  // Labels are the runs-on labels of the job, evaluated.
423  repeated string labels = 10;
424  // TimeoutMinutes is the timeout of the job in minutes: the value of the
425  // workflow's timeout-minutes, or 360 when it has none. At least 1. The
426  // runner enforces it from the moment it receives the job. The server counts
427  // from the moment of the assignment and adds 60 seconds of slack, so the
428  // runner's own timeout always comes first: when the server's deadline
429  // passes it announces CANCEL_REASON_TIMEOUT, and ends the job itself after
430  // 60 more seconds without a conclusion (see WatchJob).
431  int32 timeout_minutes = 11;
432  // Repo is the name of the repository.
433  string repo = 12;
434  // Sha is the full id of the commit the run is for, 40 lowercase
435  // hexadecimal characters for SHA-1. The runner reads the code at it.
436  string sha = 13;
437  // Ref is the full ref name that triggered the run, for example
438  // "refs/heads/main" or "refs/tags/v1.0.0".
439  string ref = 14;
440}
441
442// UpdateJobRequest reports the steps of a running job.
443message UpdateJobRequest {
444  // JobId is the Job.job_id. It must be the job of the token.
445  string job_id = 1;
446  // Steps are the steps whose state changed or that are new. Each one is a
447  // full snapshot of its step, applied by number. At most 1,000 steps per job
448  // (invalid_argument beyond). Steps are numbered like GitHub's log: the
449  // implicit "Set up job" is 1, the steps of the workflow follow in order, and
450  // the implicit "Complete job" is last.
451  repeated StepUpdate steps = 2;
452}
453
454// StepUpdate is the state of one step as the runner knows it.
455message StepUpdate {
456  // Number is the 1-based number of the step in the job. It is the number
457  // used by AppendLog.
458  int32 number = 1;
459  // Name is the display name of the step, evaluated: the "name" of the
460  // workflow step, or "Run <first line of the script>", as GitHub shows it.
461  // At most 1,024 characters; the server cuts longer names.
462  string name = 2;
463  // State is where the step is.
464  StepState state = 3;
465  // Conclusion is how the step ended. Required (not UNSPECIFIED) when state is
466  // COMPLETED, must be UNSPECIFIED otherwise. Allowed values: success,
467  // failure, cancelled, skipped.
468  CiConclusion conclusion = 4;
469  // StartedAt is when the step started, by the clock of the runner. Required
470  // from IN_PROGRESS on.
471  google.protobuf.Timestamp started_at = 5;
472  // CompletedAt is when the step ended, by the clock of the runner. Required
473  // when state is COMPLETED.
474  google.protobuf.Timestamp completed_at = 6;
475}
476
477// UpdateJobResponse is empty.
478message UpdateJobResponse {}
479
480// CompleteJobRequest ends a job.
481message CompleteJobRequest {
482  // JobId is the Job.job_id. It must be the job of the token.
483  string job_id = 1;
484  // Conclusion is how the job ended. Required: a runner reports
485  // CI_CONCLUSION_SUCCESS, CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or
486  // CI_CONCLUSION_TIMED_OUT; any other value is invalid_argument. Skipped is
487  // decided by the server and never reported by a runner.
488  CiConclusion conclusion = 2;
489  // Outputs are the outputs of the job, evaluated by the runner. Names are 1
490  // to 255 characters among letters, digits, "_" and "-" and begin with a
491  // letter or "_" and are case-insensitive: two names that differ only by case
492  // are invalid_argument. Each value is at most 1 MiB, and all names and
493  // values together at most 1 MiB (1,048,576 bytes); beyond that the whole call
494  // is rejected with invalid_argument and the runner reports the failure of
495  // the job without outputs, with a line in the log.
496  map<string, string> outputs = 3;
497  // RunnerShutdown is true when the runner ends the job because it was told to
498  // stop (service stopped, signal). Only allowed with CI_CONCLUSION_CANCELLED.
499  // The server then annotates the job with GitHub's message "The runner has
500  // received a shutdown signal. This can happen when the runner service is
501  // stopped, or a manually started runner is canceled." The conclusion
502  // CANCELLED for this case is to be verified against a recording of GitHub.
503  bool runner_shutdown = 4;
504  // Steps are the final steps, with the same meaning as UpdateJobRequest.steps,
505  // applied in the same atomic step as the conclusion.
506  repeated StepUpdate steps = 5;
507}
508
509// CompleteJobResponse is the stored result of a job.
510message CompleteJobResponse {
511  // Conclusion is the conclusion of the job as the server stored it. It is the
512  // one of the request when applied is true.
513  CiConclusion conclusion = 1;
514  // Error is the error text stored with the job, as RunJob.error. Empty when
515  // there is none.
516  string error = 2;
517  // Applied is true when this call ended the job, false when the job had
518  // already ended (a retry, or the server ended it first) and nothing changed.
519  bool applied = 3;
520}
521
522// JobEnded says that a job has ended. It is sent in WatchJob and attached, as
523// an error detail, to the failed_precondition errors that RunnerService returns
524// for a job that has ended.
525message JobEnded {
526  // Conclusion is how the job ended.
527  CiConclusion conclusion = 1;
528  // Error is the error text stored with the job, as RunJob.error. Empty when
529  // there is none.
530  string error = 2;
531}
532
533// AppendLogRequest carries log lines of one step.
534message AppendLogRequest {
535  // JobId is the Job.job_id. It must be the job of the token.
536  string job_id = 1;
537  // Step is the number of the step the lines belong to (see
538  // UpdateJobRequest.steps), at least 1, and at least the step of the last
539  // stored line. A step that UpdateJob has not announced yet is created by the
540  // first lines. See the rules on AppendLog.
541  int32 step = 2;
542  // Offset is the number of the first line of the request in the log of the
543  // job, counted from 0 across all steps. See the rules on AppendLog.
544  int64 offset = 3;
545  // Lines are the lines to append, in order: at most 1,000, at most 64 KiB of
546  // text in total.
547  repeated LogLine lines = 4;
548}
549
550// LogLine is one line of a job log.
551message LogLine {
552  // Time is when the line was produced, by the clock of the runner. The server
553  // keeps it with nanosecond precision (RFC 3339) and does not reorder lines.
554  google.protobuf.Timestamp time = 1;
555  // Text is the line without its line terminator, valid UTF-8 (the client
556  // replaces invalid bytes with U+FFFD). It must not contain a line feed; a
557  // carriage return is kept. It is the line as GitHub's raw log has it,
558  // including "##[group]" and "##[endgroup]" markers and "##[error]" lines.
559  string text = 2;
560  // Continued is true when this line is the continuation of the line before
561  // it, which was too long to send whole. A runner cuts a line of more than
562  // 64 KiB into pieces at UTF-8 character boundaries, each piece of at most
563  // 64 KiB, and sets continued on every piece but the first. Each piece is one
564  // line for numbering and offsets; the server and the raw log join the
565  // pieces back, so the original line is rebuilt exactly. The first line of a
566  // job cannot be continued (invalid_argument) and a continued line is in
567  // the same step as the line before it (failed_precondition otherwise).
568  bool continued = 3;
569}
570
571// AppendLogResponse tells how much of the log the server holds.
572message AppendLogResponse {
573  // NextOffset is the number of lines of the job log after the call, which is
574  // the offset of the next request.
575  int64 next_offset = 1;
576}
577
578// HeartbeatRequest names the job that is alive.
579message HeartbeatRequest {
580  // JobId is the Job.job_id. It must be the job of the token.
581  string job_id = 1;
582}
583
584// HeartbeatResponse is empty.
585message HeartbeatResponse {}
586
587// WatchJobRequest names the job to watch.
588message WatchJobRequest {
589  // JobId is the Job.job_id. It must be the job of the token.
590  string job_id = 1;
591}
592
593// WatchJobResponse is one message of the stream.
594message WatchJobResponse {
595  // Msg is what the message carries. New kinds may be added: a client ignores
596  // the ones it does not know.
597  oneof msg {
598    // Keepalive: nothing to do, the stream is alive.
599    Keepalive keepalive = 1;
600    // Cancel: the job must stop.
601    CancelRequest cancel = 2;
602    // Ended: the job has ended. The last message of a stream: a close without
603    // it means the client reconnects.
604    JobEnded ended = 3;
605  }
606}
607
608// Keepalive has no fields.
609message Keepalive {}
610
611// CancelRequest asks the runner to stop a job.
612message CancelRequest {
613  // Reason is why the job must stop. New reasons may be added; a runner that
614  // does not know one cancels the job and reports CI_CONCLUSION_CANCELLED.
615  CancelReason reason = 1;
616  // Message is a short human-readable explanation, for the job log, for
617  // example "Canceled by the owner". At most 1,024 characters.
618  string message = 2;
619  // RequestedAt is when the cancellation was requested, by the clock of the
620  // server.
621  google.protobuf.Timestamp requested_at = 3;
622}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 18

↑↓ moveenter openesc close