Skip to content
1,527 linesCodeBlameRaw
1---
2title: GitHub Actions
3description: Your GitHub Actions workflows run on g1t as they are. Rename .github to .g1t and push.
4---
5
6g1t runs GitHub Actions workflows. They are written exactly as on GitHub,
7and kept in `.g1t/workflows/` instead of `.github/workflows/`.
8
9## Moving from GitHub
10
11```sh
12git mv .github .g1t
13git commit -m "Run our workflows on g1t"
14git push g1t main
15```
16
17That is the whole move. Everything in the folder comes along: workflows,
18local actions under `.g1t/actions/` and anything else you keep there.
19Workflows that still say `uses: ./.github/actions/setup` find it under
20`.g1t/` once `.github` is gone.
21
22Otherwise g1t doesn't read `.github`, so a repository that lives in both
23places can keep `.github` for GitHub and `.g1t` for g1t, side by side.
24
25### On a mirror
26
27A [mirror](/guides/mirroring/) of a GitHub repository runs nothing while it
28stands by: its workflows run on GitHub. Its owners can change that:
29
30- **CI failover** runs its workflows on g1t for each push copied in from
31 GitHub, `.github/workflows` as well as `.g1t/workflows`. Use it when
32 GitHub's workflows aren't running.
33- **Taking over** runs them for everything pushed to g1t, until it's handed
34 back.
35- **Keep CI warm** runs `.g1t/workflows`, and only those, on each push
36 copied in while it stands by.
37
38When both folders have a workflow of the same `name:`, the one in `.g1t`
39runs. A workflow that deploys (any job names an `environment:`) waits for
40approval during CI failover and takeovers, so it doesn't deploy from two
41places, unless the mirror's settings say otherwise. A push copied in from
42GitHub that changes workflows or local actions waits for approval before
43it can use the repository's secrets.
44
45Then add your [secrets and variables](#secrets-and-variables): GitHub never
46gives their values out, so they cannot be copied across.
47
48## What runs
49
50| On GitHub | On g1t |
51| --- | --- |
52| `on:` `push` (branches, tags, paths), `pull_request`, `pull_request_target`, `issues`, `issue_comment`, `pull_request_review`, `schedule`, `workflow_dispatch`, `workflow_run`, `merge_group`, `create`, `repository_dispatch`, `release`, `deployment`, `deployment_status` | The same, from g1t's own pushes, pull requests, issues, comments, [releases](#releases), [deployments](#deployments) and [merge queue](/guides/merge-queue/). `create` starts on each new branch or tag; `repository_dispatch` on [a dispatch event](#repository-dispatch). |
53| `jobs`, `needs`, `if`, `outputs`, `env`, `defaults`, `timeout-minutes`, `continue-on-error` | The same. |
54| `timeout-minutes` and `continue-on-error` on a step | The same, for `run:` and `uses:` steps alike. A `uses:` step's action is stopped at its limit, with every process it started; a step inside a composite action stops at its own limit or the `uses:` step's, whichever comes first. A step stopped this way fails, unless `continue-on-error` lets the job go on. |
55| `strategy.matrix` with `include` and `exclude`, `fail-fast`, `max-parallel`, a matrix from `fromJSON(needs.…)` | The same. |
56| `concurrency` with `cancel-in-progress`, for the workflow or for one job | The same: one run, or one job, of a group at a time. |
57| `permissions:` for the workflow or for one job, `read-all`, `write-all` | The same: they decide what [the job's token](#the-jobs-token) may do. |
58| `${{ }}` expressions: every operator, function and context | The same, including `hashFiles`, `success()`, `failure()`, `always()` and `cancelled()`. |
59| `run:` with `bash`, `sh`, `python` or a custom shell | The same. |
60| JavaScript actions (`uses: owner/repo@v7`, `owner/repo/path@v7`) | Fetched from that repository on g1t when g1t has it and your repository may use it, otherwise from GitHub, and run as they are, on Node 24, the runtime current actions declare. See [actions and workflows from other repositories](#actions-and-workflows-from-other-repositories). |
61| Composite actions | The same. |
62| Reusable workflows (`jobs.<id>.uses: ./.g1t/workflows/build.yml`, or `owner/repo/.g1t/workflows/build.yml@v1` in another repository) | The same: `with:` inputs, `secrets:` by name or `secrets: inherit`, `on.workflow_call` outputs, and nesting up to four deep. `.github/workflows/…` finds the workflow under `.g1t/` after the move. See [actions and workflows from other repositories](#actions-and-workflows-from-other-repositories). |
63| `actions/checkout` | Checks out from g1t, with `ref`, `fetch-depth`, `path`, `repository`, `token` and `submodules`. |
64| `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH`, `GITHUB_STATE`, `GITHUB_STEP_SUMMARY` | The same. Step summaries show on the run's page; see [job summaries](#job-summaries). |
65| `::error::`, `::warning::`, `::notice::`, `::group::`, `::add-mask::` | The same: errors and warnings become annotations on the run, and [masked](#masking-secrets) values stay hidden. |
66| `secrets.*`, `vars.*`, `secrets.GITHUB_TOKEN` | The same. `secrets.G1T_TOKEN` is [the job's own token](#the-jobs-token); `GITHUB_TOKEN` is its alias. |
67| `environment:` on a job | The job waits for the environment's [protection rules](#environments), then reads each key's row for that environment, as environment secrets work, and the run records a [deployment](/guides/deployments-api/#deployments-from-g1t-actions) to it. `url` gives the deployment its address; `deployment: false` reads the environment's values without making one. The name may be an expression. |
68| `actions/upload-artifact`, `actions/download-artifact`, `actions/upload-artifact/merge` | The same inputs and outputs as version 4: `retention-days`, `overwrite`, `compression-level`, `include-hidden-files`, `!` exclusions, download by `pattern` with `merge-multiple`, and from another run with `run-id` and `github-token`. Up to 5 GiB each; see [artifacts](#artifacts). |
69| `actions/cache`, `actions/cache/restore`, `actions/cache/save` | Kept per repository and branch, found by `key` or the newest under a `restore-keys` prefix. `path` takes globs and `!` exclusions. Up to 2 GiB each; see [the cache](#the-cache). |
70| Actions that cache through the toolkit, such as `actions/setup-node` with `cache: npm` or `Swatinem/rust-cache` | The same: they save to and restore from the repository's cache. See [actions built on the toolkit](#actions-built-on-the-toolkit). |
71| sccache with `SCCACHE_GHA_ENABLED` | The same: its entries go to the repository's cache. See [caching Rust builds](#caching-rust-builds). |
72| `permissions: id-token: write` | The job can ask for an OIDC token, and trade it for a cloud provider's credentials. See [OIDC tokens](#oidc-tokens). |
73| `docker build`, `push`, `run`, `login`, `compose`, Buildx | The same, with a Docker Engine of the job's own. See [Docker](#docker). |
74| `services:` | The same: each service starts before the steps, health checks are waited for, and it is reached at `localhost` on its port and by its name. |
75| `container:` | The same: every step runs inside the image. |
76| `uses: docker://image`, Docker actions (`runs.using: docker`) | The same: built from the action's Dockerfile or pulled, and run with GitHub's `/github/workspace` layout. |
77| `docker/setup-buildx-action`, `docker/build-push-action`, `docker/login-action` | The same. `setup-buildx-action` picks the job's own Engine as the builder. |
78
79The **Actions** page of a workflow says, under *How this runs on g1t*,
80anything in it that runs differently.
81
82### Not yet
83
84- **Windows and macOS on g1t's machines.** g1t's own runners are Linux; a
85 job with `runs-on: windows-latest` or `macos-latest` fails, and says so.
86 [Self-hosted runners](/guides/self-hosted-runners/) of any OS run them:
87 `runs-on: [self-hosted, windows]`.
88- **Docker's `type=gha` build cache.** Buildx skips it on g1t, and the
89 build runs without a cache. Use a registry cache instead; see
90 [caching image builds](#caching-image-builds).
91- **Actions that upload artifacts with the toolkit's artifact library
92 themselves.** The library refuses to run against any server but
93 github.com. `actions/upload-artifact`, `actions/download-artifact` and
94 `actions/upload-artifact/merge` work, because g1t runs them itself. See
95 [actions built on the toolkit](#actions-built-on-the-toolkit).
96- **`on: delete`.** Deleting a branch or tag starts no workflows yet; the
97 workflow's page says so.
98
99Why each of these is missing, and what to use instead, is on
100[What g1t can't do yet](/about/limitations/#actions-and-runners).
101
102## Schedules
103
104A workflow with `on: schedule` runs on the default branch's latest commit,
105at each time its cron lines name, in UTC:
106
107```yaml
108on:
109 schedule:
110 - cron: "0 9 * * mon" # Mondays at 09:00 UTC
111 - cron: "*/30 * * * *" # every 30 minutes
112```
113
114Each line has five fields: minute, hour, day of month, month and day of
115the week. A field takes `*`, a number, a range `1-5`, a list `1,15` and a
116step `*/10`; days take `mon` to `sun`, and months `jan` to `dec`.
117
118| Rule | What happens |
119| --- | --- |
120| Every 5 minutes at most | A schedule more frequent than every 5 minutes, such as `* * * * *` or `*/2 * * * *`, runs every 5 minutes instead, on the five-minute marks (:00, :05, :10 and so on), at each mark that ends five minutes in which it would have run. A mark outside the hours, days or months the schedule names never runs: `* 9 * * *` runs from 09:00 to 09:55. |
121| No push for 60 days | Schedules pause in a repository that has had no push for 60 days. The next push to any branch resumes them. Other events and `workflow_dispatch` still start the workflow. |
122| Actions not paid for | When a scheduled run's job could not start because the workspace's plan, spend limit or the open-source pool does not cover it, that run fails and says why, and the workflow's schedule waits an hour before it tries again. |
123| Archived repository | Schedules wait until the repository is unarchived. |
124
125## Actions and workflows from other repositories
126
127A step's `uses: owner/repo@ref` (or `owner/repo/path@ref`) and a job's
128`uses: owner/repo/.g1t/workflows/build.yml@ref` name another repository.
129g1t looks for it on g1t first:
130
131| The repository | What happens |
132| --- | --- |
133| On g1t and public | Your workflows use it, from any workspace. |
134| On g1t, private, in your workspace, with **Access** set to *Accessible from repositories in* the workspace | Your private repositories' workflows use it. A job reads it with a read-only token for that repository alone, which ends with the job. |
135| On g1t, private, and not shared that way | The step or job fails, and says why. A private repository's actions are never used by a public repository's workflows, whose logs anyone can read, nor from another workspace. |
136| Not on g1t, or private in a workspace you cannot see | An action comes from GitHub, at the commit its ref names there (see [actions from GitHub](#actions-from-github)); a reusable workflow is read from a public repository on GitHub. |
137
138`ref` is a branch, a tag or a commit. A reusable workflow may be under
139`.g1t/workflows/` or `.github/workflows/`; a `.github/workflows/` path
140also finds the file under `.g1t/workflows/` in a repository moved to
141g1t. A `./.g1t/workflows/…` call inside a workflow from another
142repository reads from that repository, at the same ref.
143
144To share a private repository's actions and workflows with the rest of its
145workspace, an admin chooses **Settings → Actions → Access → Accessible from
146repositories in** the workspace, or calls
147`PUT /repos/{owner}/{repo}/actions/permissions/access` with
148`{"access_level": "organization"}` (`none` to stop).
149
150### Actions from GitHub
151
152g1t keeps the actions your workflows use from GitHub, so a job downloads
153them from g1t rather than from GitHub:
154
1551. Before the first step, the job downloads every action its steps name,
156 as GitHub's runner does in **Set up job**, and fails there if one can't
157 be had. Its log names each one's commit:
158 `Download action repository 'actions/setup-node@v4' (SHA:…) from g1t`.
1592. g1t reads which commit the ref (`v4`, a branch, or a commit) names on
160 GitHub, a tag before a branch of the same name, and looks again at
161 most every 5 minutes, so a tag that moves is followed within minutes.
1623. The first job to use an action at a commit has g1t fetch that commit's
163 archive from GitHub and keep it. Every later job, in any workspace,
164 downloads the same bytes from g1t.
165
166While GitHub doesn't answer, a ref reads as the commit it last named, and
167the job says so in a warning; an action g1t keeps still downloads. An
168action whose archive is larger than 32 MB, or from a private repository
169on GitHub, is downloaded from GitHub by the job itself. An archive no job
170has used for 30 days is deleted, and fetched again when next used.
171Kept archives are g1t's and count toward no workspace's storage.
172
173Actions that a composite action uses are downloaded when it runs.
174
175### Secrets for a called workflow
176
177A called workflow gets only the secrets its caller passes, plus
178`G1T_TOKEN` (`GITHUB_TOKEN`):
179
180```yaml
181jobs:
182 build:
183 uses: acme/shared/.g1t/workflows/build.yml@v2
184 with:
185 node-version: 24
186 secrets:
187 npm-token: ${{ secrets.NPM_TOKEN }}
188
189 deploy:
190 uses: ./.g1t/workflows/deploy.yml
191 secrets: inherit
192```
193
194- `secrets:` with names passes each as the called workflow names it,
195 read from the caller's `secrets`, `needs`, `inputs`, `matrix`,
196 `github` and `vars`.
197- `secrets: inherit` passes every secret the caller has.
198- A job in the called workflow with its own `environment:` also reads that
199 environment's secrets, over what was passed.
200- A secret the called workflow marks `required: true` under
201 `on.workflow_call.secrets` that the caller does not pass fails the
202 calling job before anything runs.
203
204`vars` are the calling repository's, and a called workflow's jobs run
205with the calling run's `github` context: `actions/checkout` checks out the
206calling repository.
207
208## Releases
209
210Workflows with `on: release` start when a release changes, at the commit
211its tag names (`GITHUB_REF` is `refs/tags/<tag>`). Each change is one or
212more activity types, which `types:` chooses among:
213
214| Change | Activity types |
215| --- | --- |
216| A draft made | `created` |
217| A release made and published | `created`, `published`, and `released` (or `prereleased` for a prerelease) |
218| A draft published | `published`, and `released` or `prereleased` |
219| A prerelease made a full release | `edited` and `released` |
220| Made a draft again | `unpublished` |
221| Title, notes or prerelease changed | `edited`, with `github.event.changes` holding the old title and notes |
222| Deleted (the tag stays) | `deleted` |
223
224```yaml
225on:
226 release:
227 types: [published]
228```
229
230`github.event.release` has `tag_name`, `name`, `body`, `draft`,
231`prerelease`, `target_commitish`, `author` and `html_url`. A release a
232job's own token makes or changes starts no workflows.
233
234## Deployments
235
236`on: deployment` starts when a deployment is made, and
237`on: deployment_status` when one has a new status: one reported through
238the [deployments API](/guides/deployments-api/) or a
239[g1t.page](/guides/deployments/) build. The run is at the commit deployed;
240`GITHUB_REF` is the branch or tag deployed, and empty for a bare commit.
241
242```yaml
243on: deployment_status
244
245jobs:
246 smoke:
247 if: github.event.deployment_status.state == 'success'
248 runs-on: ubuntu-latest
249 steps:
250 - run: curl -fsS "${{ github.event.deployment_status.environment_url }}"
251```
252
253`github.event.deployment` has `environment`, `ref`, `sha`, `task` and
254`payload`; `github.event.deployment_status` has `state`, `environment_url`
255and `log_url`. Deployments a workflow makes, with `environment:` or with
256its job's token, start no workflows, so a workflow cannot set itself off.
257
258## The runner
259
260Jobs run in a fresh sandbox each: Debian with Node 24, Python 3, Go, Rust,
261Java 21, .NET 8, Ruby 3.3, `build-essential`, `git`, `curl`, `jq`, Docker
262(with Buildx and Compose) and passwordless `sudo`, in GitHub's layout
263(`/home/runner/work`, `RUNNER_TEMP`, `RUNNER_TOOL_CACHE`).
264`runner.os` is `Linux`. `ubuntu-latest`, `ubuntu-24.04` and other Linux
265labels all run here. A job whose `runs-on` names `self-hosted` waits for one
266of your [self-hosted runners](/guides/self-hosted-runners/) instead. Setup actions such as
267`actions/setup-node` and `actions/setup-python` install other versions as
268they do on GitHub.
269
270### Languages and their setup actions
271
272Each language below is on `PATH` from the job's first step, so a workflow
273that only runs `java`, `dotnet` or `ruby` needs no setup step. When it has
274one, the setup action finds the version that is already there and
275downloads nothing.
276
277| Language | Version | Where | Setup action |
278| --- | --- | --- | --- |
279| Java | Eclipse Temurin 21 (LTS), JDK | `JAVA_HOME` (also `JAVA_HOME_21_X64`), in `RUNNER_TOOL_CACHE` | `actions/setup-java` with `distribution: temurin` and `java-version: 21` uses it. Other versions and distributions are downloaded. |
280| .NET | SDK 8 (LTS) | `DOTNET_ROOT`, `/usr/share/dotnet` | `actions/setup-dotnet` with `dotnet-version: 8.0.x` keeps it when it is the newest 8.0 SDK, and installs other SDKs beside it. |
281| Ruby | 3.3, with Bundler | in `RUNNER_TOOL_CACHE` | `ruby/setup-ruby` with `ruby-version: '3.3'` (or a `.ruby-version` naming 3.3) uses it. |
282| Node | 24 | `/usr/local/bin` | `actions/setup-node` installs other versions. |
283| Python | 3.11 | `/usr/bin/python3` | `actions/setup-python` installs other versions. |
284| Go | 1.27 | `/usr/local/go` | `actions/setup-go` installs other versions. |
285| Rust | stable, with `rustfmt`, `clippy` and the `wasm32-unknown-unknown` target | `~/.cargo/bin` | `rustup` is there to add toolchains and targets. |
286
287```yaml
288steps:
289 - uses: actions/checkout@v5
290 - uses: actions/setup-java@v5
291 with:
292 distribution: temurin
293 java-version: 21
294 - run: ./gradlew build
295```
296
297Because the sandbox runs Debian, `ruby/setup-ruby` treats it as a
298self-hosted runner and uses only the Rubies in `RUNNER_TOOL_CACHE`. A version other than 3.3 fails at that step; install
299it in a `run` step instead (for example with `ruby-build`) or run the job
300in a `container:` with the Ruby you need, such as `ruby:3.4`.
301
302The headers that gems and .NET need to build native code (`libyaml`,
303`libffi`, `zlib`, OpenSSL, ICU) are installed too.
304
305### Calling g1t's API from a job
306
307The `gh` command is not installed: it needs a GraphQL API, and g1t's API
308is REST. Call it with `curl`, using the job's token and the API's address,
309which every job has as `GITHUB_API_URL`:
310
311```yaml
312- name: Comment on the pull request
313 env:
314 TOKEN: ${{ github.token }}
315 run: |
316 curl -fsS -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/${{ github.event.number }}/comments" \
317 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
318 -d '{"body": "Built."}'
319```
320
321The token reaches this repository and does what the job's `permissions:`
322say; see [the job's token](#the-jobs-token). The
323[API reference](/reference/api/) lists every endpoint.
324
325### Machine sizes
326
327A job runs on the standard machine unless its `runs-on` names a larger
328one:
329
330| `runs-on` | vCPUs | Memory | Disk |
331| --- | --- | --- | --- |
332| `ubuntu-latest`, or any other Linux label | 0.5 | 4 GiB | 8 GB |
333| `g1t-2core` | 2 | 8 GiB | 16 GB |
334| `g1t-4core` | 4 | 12 GiB | 20 GB |
335
336```yaml
337jobs:
338 build:
339 runs-on: g1t-4core
340```
341
342The label can come from the matrix or the run's inputs
343(`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). A
344larger machine costs what it costs g1t, plus the same margin as all
345sandbox time: see [usage and billing](/guides/usage-and-billing/#workflow-jobs-on-larger-machines).
346Builds that compile, such as Rust or a large TypeScript project, finish
347several times faster on one.
348
349A job on g1t's machines runs for at most 60 minutes, whatever its
350`timeout-minutes`; one on a self-hosted runner can run for up to 24 hours.
351A job stopped at its time cap fails saying so.
352
353### What a job can reach
354
355A job's network is restricted, as an agent's is (see
356[guardrails](/guides/guardrails/)): it reaches the hosts its project's
357guardrails allow, g1t itself, and what builds need, and nothing else.
358What builds need is the package registries (npm, PyPI, crates.io, the Go
359proxy, RubyGems, Packagist, NuGet, Maven and Gradle, Debian's mirrors),
360GitHub, where `uses:` actions and the setup actions' downloads come from,
361the toolchains' download sites (`nodejs.org`, `go.dev`,
362`static.rust-lang.org`), and the public container registries (Docker Hub,
363GitHub's, Quay, and `mirror.gcr.io`, the mirror of Docker Hub that a job's
364Engine asks first). A request anywhere else gets `403` with
365the reason. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or
366higher adds it to the project's
367allowed domains under **Settings → Guardrails**; a project whose guardrails
368turn the network restriction off runs its jobs with an open network.
369
370A host only workflows should reach, such as the API a deploy uploads to,
371goes in **Workflow-only domains** instead, limited to the workflows and
372environments that need it: `api.cloudflare.com | deploy.yml | production`
373lets only `deploy.yml`'s jobs with `environment: production` reach it.
374Agents never reach those hosts, and neither do runs of pull requests from
375forks. See [workflow-only domains](/guides/guardrails/#workflow-only-domains).
376
377g1t does not run cryptocurrency miners: a step that names one (`xmrig`,
378a `stratum+tcp://` pool, `--donate-level`) is not run, and a job that
379looks like it is mining is stopped. See
380[abuse and mining](/guides/guardrails/#abuse-and-mining).
381
382## Docker
383
384Each job on g1t's machines has a Docker Engine of its own, inside the
385job's sandbox. Nothing runs until the job uses it: the first `docker`
386command, or a job's `services:` or `container:`, starts it, in a second
387or two, and the log says so. It ends with the job, with every image,
388container and build cache in it. No other job, repository or workspace
389ever shares it.
390
391```yaml
392jobs:
393 test:
394 runs-on: ubuntu-latest
395 services:
396 postgres:
397 image: postgres:17
398 env:
399 POSTGRES_PASSWORD: ${{ secrets.DB_PASSWORD }}
400 ports: ["5432:5432"]
401 options: >-
402 --health-cmd pg_isready --health-interval 5s --health-retries 10
403 steps:
404 - uses: actions/checkout@v5
405 - run: docker compose up -d --wait
406 - run: npm test
407 env:
408 DATABASE_URL: postgres://postgres:${{ secrets.DB_PASSWORD }}@localhost:5432/postgres
409```
410
411### What works
412
413| | On g1t's machines |
414| --- | --- |
415| `docker build`, `buildx build`, `run`, `exec`, `pull`, `push`, `login`, `compose` | Work as they do on GitHub's runners. The Engine, Buildx and Compose are current releases. |
416| `services:` | Pulled and started before the first step, with `env`, `ports`, `volumes`, `options` and `credentials`. Services with a health check are waited for; one that turns unhealthy fails the job with its log. Each service's log is printed when the job ends. `job.services.<id>.id`, `.network` and `.ports` are set. |
417| `container:` | Every `run` step and JavaScript action runs inside the image, with its `env`, `options`, `volumes` and `credentials`. The workspace, `RUNNER_TEMP` and the tool cache are mounted at the same paths as on g1t's runner. |
418| `uses: docker://image` | Pulled and run, with `with.args` and `with.entrypoint`. |
419| Docker actions | Built from the action's Dockerfile (or pulled, for `image: docker://…`), and run with its `args`, `env` and `entrypoint`, its inputs as `INPUT_*` variables, and `pre-entrypoint` and `post-entrypoint`. |
420| `docker/setup-buildx-action` | Selects the job's own Engine as the builder (BuildKit). Its `name`, `driver`, `platforms` and `nodes` outputs are set. `driver`, `driver-opts` and `buildkitd-*` are not used, and the log says so. |
421| `docker/build-push-action` | Works, with `push`, `load`, `tags`, `labels`, `build-args`, `secrets`, `target`, `provenance` and `sbom`. |
422| `docker/login-action` | Works, for g1t's registry, Docker Hub, GitHub's registry, Cloudflare's (`registry.cloudflare.com`) and any registry the job can reach. |
423
424### Services and the network
425
426Every container a job starts shares the job's own network, the one its
427[guardrails](/guides/guardrails/) apply to. So:
428
429- **A service is at `localhost`** on its port, from steps and from other
430 containers. `ports: ["5432:5432"]` and `ports: ["5432"]` both mean
431 `localhost:5432`.
432- **A port mapped to another number** (`ports: ["6543:5432"]`, or
433 `docker run -p 8080:80`) is forwarded: `localhost:6543` reaches the
434 service's 5432. `job.services.<id>.ports` says which port to use, and
435 `docker inspect` and `docker port` report it.
436- **A service is also reached by its name**, as it is from a job
437 container on GitHub: `postgres:5432` works from steps, from the job's
438 container and from any container started later. So do the names of
439 containers and Compose services, and their network aliases.
440- **Two containers cannot listen on the same port.** A job with a
441 `redis` service and a Compose file that starts another Redis on 6379
442 gets an error from the second; give one of them another port.
443
444A container that asks for `--network none` gets none, and
445`--network container:<name>` shares that container's.
446
447### Job containers
448
449With `container:`, the steps run inside the image as its default user,
450usually `root`. A few things differ from GitHub's runner:
451
452- The workspace is at the same path as on g1t's runner
453 (`/home/runner/work/…`), not `/__w`. `github.workspace` is correct
454 either way.
455- JavaScript actions run inside the container with g1t's Node 24, which
456 needs an image with glibc and `libstdc++` (Debian, Ubuntu and most
457 language images have both). In an image without them, such as Alpine,
458 they run beside the container, on g1t's runner, with the same files,
459 and the log says so.
460- `actions/checkout`, `actions/cache` and the artifact actions run on
461 g1t's runner, with the same files.
462
463### Building and pushing images
464
465On g1t's machines, a job is signed in to g1t's container registry from
466the start, with its own `G1T_TOKEN`, so it can push to and pull from its
467repository's images without a login step; images of other repositories
468need this one added under their
469[Manage Actions access](/guides/packages/#manage-actions-access). A run that gets no secrets is
470not signed in. See [container registry](/guides/containers/#in-workflows).
471
472```yaml
473jobs:
474 image:
475 runs-on: g1t-4core
476 steps:
477 - uses: actions/checkout@v5
478 - uses: docker/setup-buildx-action@v3
479 - uses: docker/build-push-action@v6
480 with:
481 push: true
482 tags: g1t.sh/${{ github.repository }}:${{ github.sha }}
483 cache-from: type=registry,ref=g1t.sh/${{ github.repository }}:buildcache
484 cache-to: type=registry,ref=g1t.sh/${{ github.repository }}:buildcache,mode=max
485```
486
487For other registries, sign in with `docker/login-action` or
488`docker login`, as on GitHub. Docker Hub's images are pulled through its
489public mirror first, so jobs are rarely held up by Docker Hub's limits on
490anonymous pulls.
491
492#### Caching image builds
493
494The Engine starts empty in every job, so a build's layers are rebuilt
495unless the job brings a cache:
496
497- **A registry cache** (`cache-to: type=registry,ref=…,mode=max`), in g1t's
498 registry or any other, is the simplest and is shared by every branch.
499- **A local cache** (`cache-to: type=local,dest=/tmp/buildx-cache`) saved
500 and restored with `actions/cache`, within [the cache's limits](#the-cache).
501- **`type=gha`** is not used on g1t yet: Buildx skips it, and the build
502 runs without a cache.
503
504### Limits
505
506- **Machine.** Containers share the job's machine: its vCPUs, memory and
507 disk ([machine sizes](#machine-sizes)). Image builds and databases want
508 `g1t-2core` or `g1t-4core`. `--cpus` and `--memory` limit a container
509 within that.
510- **Disk.** Images take room on the job's disk. On a machine whose disk
511 cannot hold layered images, the Engine stores plain copies, which take
512 more room; the log says when it does.
513- **Linux, amd64.** Images for other platforms need QEMU's emulators,
514 which g1t's machines do not have set up; `docker/setup-qemu-action` is
515 not supported there yet.
516- **Privileged containers** (`--privileged`) run, with no more reach than
517 the job itself has: the job's sandbox is the boundary.
518
519### How Docker is kept safe
520
521- **One Engine per job.** It runs inside the job's own sandbox, a virtual
522 machine of its own, and is gone with it. No Docker socket of g1t's, or of
523 any machine, is shared with a job.
524- **The job's guardrails hold.** Containers use the job's network, so a
525 container, a build step or an image pull reaches only what the job may
526 reach. A host off the list gets `403` with the reason, as any step does.
527- **HTTPS keeps working.** In a job whose network is restricted, every
528 container and build step is given the certificate the job's HTTPS is
529 checked with, in `/dev/g1t-egress`, and `SSL_CERT_FILE`,
530 `NODE_EXTRA_CA_CERTS`, `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE`, `PIP_CERT`,
531 `GIT_SSL_CAINFO` and `CARGO_HTTP_CAINFO` pointing at it, unless the
532 container sets them itself. None of it is written into an image's layers.
533 Tools that keep their own list of certificates, such as Java's, need it
534 added in the build that uses them.
535- **Short-lived credentials.** The registry sign-in uses the run's own
536 token, which ends with the run; `credentials:` for a service or a job
537 container are used for that pull only.
538- **No miners.** A container whose image or command names a miner is not
539 created, as a step's script is not run.
540
541## The cache
542
543`actions/cache` keeps what a job saves for the repository's later jobs:
544
545| | |
546| --- | --- |
547| One entry | Up to 2 GiB, compressed. A larger one is not saved, and the job goes on. |
548| A repository's entries | Up to 10 GiB together. Saving past it removes the entries restored longest ago. |
549| How long | Until it has not been restored for 7 days, and at most 28 days after it was saved. |
550| Which branch | An entry belongs to the branch, tag or pull request whose run saved it. A run restores from its own, then from the branch its pull request merges into, then from the default branch. |
551| Keys | Written once on each branch: saving under a key that exists there does nothing. On each branch in turn, a restore finds its `key` exactly, else the newest entry whose key starts with one of its `restore-keys`. |
552| `path` | Files and folders; globs, `**` included; `~/` is the home folder; a line starting with `!` leaves matching paths out. The same key saved for other paths is another entry. |
553| Compression | zstd. |
554
555So a feature branch can read what `main` saved, but `main` never reads
556what a feature branch saved, and a pull request from outside the
557repository saves where nothing else ever reads it: nobody can plant an
558entry that the default branch's builds restore. Actions that cache through the
559toolkit, such as `actions/setup-node` with `cache: npm`, follow the same
560rules.
561
562```yaml
563- uses: actions/cache@v4
564 with:
565 path: |
566 ~/.cargo/registry/cache
567 target/release
568 !target/**/incremental
569 key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
570 restore-keys: cargo-${{ runner.os }}-
571```
572
573Each restore and save says on the job's log how large the entry was and
574how long it took. A workspace on the plan pays for what its caches and
575[artifacts](#artifacts) hold (`Actions cache storage` on its statement),
576at R2's price plus the margin; see
577[usage and billing](/guides/usage-and-billing/#actions-cache).
578
579## Artifacts
580
581`actions/upload-artifact` keeps files a job made with its run, for later
582jobs, other runs and people:
583
584| | |
585| --- | --- |
586| One artifact | Up to 5 GiB, zipped. |
587| A run's artifacts | Up to 10 GiB together. |
588| How long | The repository's setting: 14 days unless someone with the Maintain role changes it under **Settings → Repository → Artifacts**, from 1 to 90 days. `retention-days` asks for fewer days, never more. |
589| Names | One artifact per name in a run. Uploading a name again fails, unless the upload says `overwrite: true`, which replaces it. A name is up to 256 characters, none of `" : < > \| * ? \ /`. |
590| `path` | Files, folders and globs, `**` included; a line starting with `!` leaves matching paths out. Files and folders whose names start with `.` are left out unless `include-hidden-files: true`. |
591| Compression | `compression-level` 0 (stored) to 9; 6 unless you say. |
592| Outputs | `artifact-id` (a number), `artifact-url` (its run's page) and `artifact-digest` (the SHA-256 of its zip). |
593
594```yaml
595- uses: actions/upload-artifact@v4
596 with:
597 name: web-dist
598 path: |
599 dist/
600 !dist/**/*.map
601 retention-days: 5
602 compression-level: 9
603```
604
605`actions/download-artifact` downloads one by `name` into `path`, or every
606artifact of the run, each into a folder of its name; `pattern` picks them
607by name, and `merge-multiple: true` puts them all in one folder.
608`artifact-ids` picks them by number. With `github-token` and `run-id`, it
609downloads from another run of the same repository, such as the one a
610`workflow_run` workflow follows:
611
612```yaml
613- uses: actions/download-artifact@v4
614 with:
615 name: web-dist
616 github-token: ${{ secrets.GITHUB_TOKEN }}
617 run-id: ${{ github.event.workflow_run.id }}
618```
619
620`actions/upload-artifact/merge` downloads the run's artifacts that match
621`pattern`, uploads them as one artifact (`name`, `merged-artifacts` unless
622you say), and deletes them with `delete-merged: true`.
623
624A run's summary lists its artifacts with their size and when they expire.
625Anyone who can see the run downloads them there; someone with the Write
626role can delete one before it expires.
627
628## Actions built on the toolkit
629
630Many actions save to the cache with GitHub's toolkit, `@actions/cache`,
631rather than through `actions/cache`: `actions/setup-node`,
632`actions/setup-python`, `actions/setup-go` and `actions/setup-java` with
633`cache:`, `Swatinem/rust-cache`, and others. They work on g1t as they
634are: every job gets `ACTIONS_RUNTIME_TOKEN`, `ACTIONS_CACHE_URL` and
635`ACTIONS_RESULTS_URL`, and g1t answers the toolkit's requests from the
636repository's cache.
637
638- Their entries are the repository's, under [the cache's](#the-cache)
639 limits, and are deleted the same way.
640- An entry is restored only by the same kind of save: the toolkit names a
641 version for each entry, from its paths and compression. An entry
642 `setup-node` saved is not restored by `actions/cache`, and the other way
643 round.
644- An entry the toolkit sends whole, which it does below 128 MB, is not
645 saved when it is over 100 MB, the most g1t takes in one request. The
646 step warns and the job goes on.
647- The toolkit's artifact library refuses to run against any server but
648 github.com, so an action that uploads artifacts with it directly fails
649 with its own message. `actions/upload-artifact`,
650 `actions/download-artifact` and `actions/upload-artifact/merge` work:
651 g1t runs those itself.
652
653## Caching Rust builds
654
655A checkout gives every file a new modification time, so Cargo compiles
656your workspace's own crates again even when `target/` was restored from
657the cache. [sccache](https://github.com/mozilla/sccache) caches each
658compiler call by what it compiles (the source, the flags, the toolchain
659and the dependencies), so a crate that did not change comes back from the
660cache instead. Its GitHub Actions backend works on g1t as it is: it keeps
661its entries in the repository's cache, under [the cache's](#the-cache)
662limits and branch rules.
663
6641. Install sccache, pinned to a release and checked against its checksum.
6652. Set `RUSTC_WRAPPER: sccache` and `SCCACHE_GHA_ENABLED: "true"`. The
666 job's `ACTIONS_RUNTIME_TOKEN` and `ACTIONS_CACHE_URL` are already in
667 every step's environment; no step needs to export them.
6683. Set `CARGO_INCREMENTAL: "0"`: sccache does not cache incremental
669 builds, and a fresh checkout gains nothing from them.
670
671```yaml
672name: CI
673on:
674 pull_request:
675 push:
676 branches: [main]
677
678jobs:
679 test:
680 runs-on: ubuntu-latest
681 env:
682 RUSTC_WRAPPER: sccache
683 SCCACHE_GHA_ENABLED: "true"
684 CARGO_INCREMENTAL: "0"
685 steps:
686 - uses: actions/checkout@v5
687 - name: Install sccache
688 env:
689 VERSION: v0.18.0
690 SHA256: 45f1447fbe231e3037bde351ef70677dd212216c8d62ae7ca409fecc4d6acc89
691 run: |
692 name="sccache-$VERSION-x86_64-unknown-linux-musl"
693 curl -fsSL -o "$RUNNER_TEMP/sccache.tar.gz" \
694 "https://github.com/mozilla/sccache/releases/download/$VERSION/$name.tar.gz"
695 echo "$SHA256 $RUNNER_TEMP/sccache.tar.gz" | sha256sum -c -
696 tar -xzf "$RUNNER_TEMP/sccache.tar.gz" -C "$RUNNER_TEMP"
697 install -m 0755 "$RUNNER_TEMP/$name/sccache" "$HOME/.cargo/bin/sccache"
698 - run: cargo test --workspace --locked
699 - name: sccache's hits and misses
700 if: always()
701 run: |
702 echo '```' >> "$GITHUB_STEP_SUMMARY"
703 sccache --show-stats | tee -a "$GITHUB_STEP_SUMMARY"
704 echo '```' >> "$GITHUB_STEP_SUMMARY"
705```
706
707What to expect:
708
709| | |
710| --- | --- |
711| What is cached | Every library crate (`rlib`): your workspace's crates and their dependencies. |
712| What is compiled each time | What rustc links: binaries, `cdylib` crates, proc macros, build scripts and test harnesses. |
713| Its entries | One per compiled crate, often thousands for a workspace, each a few KB to a few MB. They count toward the repository's 10 GiB like any other entry, and the ones not restored for 7 days are deleted. |
714| Branches | A pull request reads what the default branch saved. Run the workflow on pushes to the default branch too, as above, or each pull request's first run starts with an empty cache. |
715| Starting over | Set `SCCACHE_GHA_VERSION` to any new value. A new sccache release starts over by itself. |
716| If the cache cannot be reached | sccache refuses to start. Set `SCCACHE_IGNORE_SERVER_IO_ERROR: "1"` to have rustc run without it instead. |
717
718sccache adds to `actions/cache` rather than replacing it: keep caching
719`~/.cargo/registry/cache` and `target/` keyed by `Cargo.lock`, so Cargo
720does not call rustc at all for dependencies that did not change, and
721sccache answers the calls it still makes for your own crates.
722`Swatinem/rust-cache` works the same way.
723
724## OIDC tokens
725
726A job can prove which repository, branch and environment it runs for with
727a short-lived OpenID Connect token signed by g1t, and trade it for a cloud
728provider's credentials. Nothing long-lived needs to sit in a secret.
729
7301. Give the job, or the workflow, `permissions: id-token: write`. A job
731 without it gets no token, and neither does a run of a pull request from
732 outside the repository.
7332. Tell your cloud to trust g1t's issuer for your repository (below).
7343. Use the provider's own login action, which asks for the token.
735
736```yaml
737permissions:
738 id-token: write
739 contents: read
740
741jobs:
742 deploy:
743 runs-on: ubuntu-latest
744 environment: production
745 steps:
746 - uses: aws-actions/configure-aws-credentials@v4
747 with:
748 role-to-assume: arn:aws:iam::123456789012:role/acme-web-deploy
749 aws-region: us-east-1
750```
751
752The job gets `ACTIONS_ID_TOKEN_REQUEST_URL` and
753`ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which `core.getIDToken()` reads. To ask
754for a token yourself, name its audience:
755
756```sh
757curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
758 "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://deploy.example.com" | jq -r .value
759```
760
761| | |
762| --- | --- |
763| Issuer | `https://api.g1t.sh/actions/oidc` |
764| Discovery | `https://api.g1t.sh/actions/oidc/.well-known/openid-configuration` |
765| Keys | `https://api.g1t.sh/actions/oidc/.well-known/jwks`, RS256, each with its `kid` |
766| Lifetime | 5 minutes |
767| Audience | What the job asks for; `https://g1t.sh/<owner>` when it asks for none |
768
769### Claims
770
771Each token carries the claims GitHub's do, so trust policies written for
772those read g1t's the same way.
773
774| Claim | Example |
775| --- | --- |
776| `sub` | `repo:acme/web:environment:production` for a job with an `environment:`; `repo:acme/web:pull_request` for a pull request's run; otherwise `repo:acme/web:ref:refs/heads/main` (or `refs/tags/v1.2.0`) |
777| `repository`, `repository_owner` | `acme/web`, `acme` |
778| `repository_id`, `repository_owner_id` | g1t's ids for them, such as `rep_01kpw0…` |
779| `repository_visibility` | `public` or `private` |
780| `ref`, `ref_type`, `ref_protected`, `sha` | `refs/heads/main`, `branch`, `"true"`, the commit |
781| `head_ref`, `base_ref` | A pull request's branches |
782| `environment` | The job's environment, when it has one |
783| `event_name` | `push`, `pull_request`, `workflow_dispatch`, … |
784| `workflow`, `workflow_ref`, `workflow_sha` | `Deploy`, `acme/web/.g1t/workflows/deploy.yml@refs/heads/main`, the commit |
785| `job_workflow_ref`, `job_workflow_sha` | The workflow that defines the job: a called workflow's own file |
786| `run_id`, `run_number`, `run_attempt` | `run_01kq9c…`, `"12"`, `"1"` |
787| `actor`, `actor_id` | Who started the run |
788| `runner_environment` | `github-hosted` on g1t's machines, `self-hosted` on yours |
789| `iss`, `aud`, `jti`, `iat`, `nbf`, `exp` | As in any OIDC token |
790
791### AWS
792
7931. Add g1t as an identity provider, under **IAM → Identity providers**:
794 provider type **OpenID Connect**, provider URL
795 `https://api.g1t.sh/actions/oidc`, audience `sts.amazonaws.com`. Or:
796
797 ```sh
798 aws iam create-open-id-connect-provider \
799 --url https://api.g1t.sh/actions/oidc \
800 --client-id-list sts.amazonaws.com
801 ```
802
8032. Give the role a trust policy for your repository:
804
805 ```json
806 {
807 "Version": "2012-10-17",
808 "Statement": [{
809 "Effect": "Allow",
810 "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/api.g1t.sh/actions/oidc" },
811 "Action": "sts:AssumeRoleWithWebIdentity",
812 "Condition": {
813 "StringEquals": {
814 "api.g1t.sh/actions/oidc:aud": "sts.amazonaws.com",
815 "api.g1t.sh/actions/oidc:sub": "repo:acme/web:environment:production"
816 }
817 }
818 }]
819 }
820 ```
821
8223. Use `aws-actions/configure-aws-credentials@v4` with `role-to-assume`,
823 as above.
824
825### Google Cloud
826
8271. Make a workload identity pool and a provider for g1t:
828
829 ```sh
830 gcloud iam workload-identity-pools create g1t --location=global
831 gcloud iam workload-identity-pools providers create-oidc g1t \
832 --location=global --workload-identity-pool=g1t \
833 --issuer-uri=https://api.g1t.sh/actions/oidc \
834 --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
835 --attribute-condition="assertion.repository_owner == 'acme'"
836 ```
837
8382. Let the repository act as a service account:
839
840 ```sh
841 gcloud iam service-accounts add-iam-policy-binding deploy@acme-prod.iam.gserviceaccount.com \
842 --role=roles/iam.workloadIdentityUser \
843 --member="principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/g1t/attribute.repository/acme/web"
844 ```
845
8463. Use `google-github-actions/auth@v2` with
847 `workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/g1t/providers/g1t`
848 and `service_account`. It asks for the provider's own name as the
849 audience, which the provider accepts unless you change its allowed
850 audiences.
851
852### Azure
853
8541. On the app registration or user-assigned managed identity, add a
855 federated credential with the scenario **Other issuer**: issuer
856 `https://api.g1t.sh/actions/oidc`, subject identifier
857 `repo:acme/web:environment:production`, audience
858 `api://AzureADTokenExchange`. Or:
859
860 ```sh
861 az ad app federated-credential create --id <application id> --parameters '{
862 "name": "g1t-acme-web-production",
863 "issuer": "https://api.g1t.sh/actions/oidc",
864 "subject": "repo:acme/web:environment:production",
865 "audiences": ["api://AzureADTokenExchange"]
866 }'
867 ```
868
8692. Give it a role on what it deploys to, as for any identity.
8703. Use `azure/login@v2` with `client-id`, `tenant-id` and
871 `subscription-id`, and no secret.
872
873A federated credential matches the subject exactly: add one per
874environment or branch that deploys.
875
876### Cloudflare
877
878Cloudflare's API takes API tokens, not OIDC tokens. Keep a token scoped
879to what the workflow deploys in a secret, available to that workflow only
880(see [workflow-only domains](/guides/guardrails/#workflow-only-domains)
881for limiting where it can be sent).
882
883A Worker of your own can trust g1t's jobs directly, by checking the token
884a job sends it against g1t's keys:
885
886```ts
887import { createRemoteJWKSet, jwtVerify } from "jose";
888
889const keys = createRemoteJWKSet(new URL("https://api.g1t.sh/actions/oidc/.well-known/jwks"));
890
891export async function fromG1tJob(request: Request): Promise<boolean> {
892 const token = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
893 const { payload } = await jwtVerify(token, keys, {
894 issuer: "https://api.g1t.sh/actions/oidc",
895 audience: "https://deploy.example.com",
896 });
897 return payload.sub === "repo:acme/web:environment:production";
898}
899```
900
901### npm
902
903npm's trusted publishing and provenance accept OIDC tokens only from the
904CI services npm lists, and g1t is not one of them yet. Publish with a
905granular access token in a secret instead:
906
907```yaml
908- uses: actions/setup-node@v4
909 with:
910 node-version: 24
911 registry-url: https://registry.npmjs.org
912- run: npm publish
913 env:
914 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
915```
916
917See [What g1t can't do yet](/about/limitations/#no-npm-trusted-publishing-or-provenance).
918
919## Runs and logs
920
921Open a repository's **Actions** page, in its sidebar. Pick a workflow to
922see its runs, run it by hand if it has `workflow_dispatch`, or turn it off
923without touching its file.
924
925A run's page opens on its **Summary**. Its sidebar lists **All jobs**;
926pick one to see its steps and their logs as they are written. Groups
927fold, errors and warnings are marked, and secrets are replaced with
928`***`. The page keeps itself up to date while the run goes on.
929
930The start of each job's log lists what its [token](#the-jobs-token) may do.
931
932### The run's summary
933
934The summary holds, top to bottom:
935
936| Part | What it shows |
937| --- | --- |
938| The run | What triggered it and when, who, the commit and its branch (or pull request), how the run stands, how long it took in all, and how many artifacts it kept. |
939| The jobs graph | The workflow file and its event (`deploy.yml · on: push`), then every job and how they depend on each other. See [the jobs graph](#the-jobs-graph). |
940| Annotations | Every job's errors, warnings and notices, counted by kind, each with the job it came from. Folds away. |
941| Job summaries | What each job's steps wrote to `$GITHUB_STEP_SUMMARY`, under `<job> summary`. See [job summaries](#job-summaries). |
942| Artifacts | What the run kept, to download. The count in the run's card jumps here. |
943
944Under **Run details** in the sidebar, **Workflow file** opens the
945workflow as it was at the run's commit.
946
947### The jobs graph
948
949Jobs are laid out left to right by their `needs:`: a job that needs
950nothing starts a row on the left, and each job sits one column to the
951right of the furthest job it needs. Within a column, jobs are ordered so
952that as few connectors as possible cross.
953
954| Node | How it shows |
955| --- | --- |
956| A job | Its status, its name and how long it took. Click it to open its steps and logs. |
957| A job that deploys | Its environment's address (`environment.url`), as a link, under its name. |
958| A matrix | One node with how many jobs it made (`test · 6 jobs`) and how they stand together. Click it to list each combination; click one to open it. |
959| A reusable workflow | A box under the job that calls it, named with the called file, holding the called workflow's jobs laid out the same way. The jobs that need the calling job connect to the box. |
960
961Connectors run from a job to each job that needs it. They are red from a
962job that failed, and move while the job they lead to is running (they
963stay still if your system asks for reduced motion).
964
965A wide graph scrolls sideways inside its card, on a phone as on a desktop.
966
967The sidebar groups jobs the same way: a matrix's jobs, and a called
968workflow's jobs, fold under their name.
969
970### Job summaries
971
972Markdown a step appends to the file in `$GITHUB_STEP_SUMMARY` shows on
973the run's summary, below the graph, a card per job, in the order its
974steps wrote it. The card's title opens the job:
975
976```yaml
977- name: Report the tests
978 if: always()
979 run: |
980 echo "### Test results" >> "$GITHUB_STEP_SUMMARY"
981 echo "| Suite | Passed | Failed |" >> "$GITHUB_STEP_SUMMARY"
982 echo "| --- | ---: | ---: |" >> "$GITHUB_STEP_SUMMARY"
983 echo "| unit | 41 | 0 |" >> "$GITHUB_STEP_SUMMARY"
984```
985
986| What | How it works |
987| --- | --- |
988| Formatting | GitHub-flavoured Markdown: tables, task lists, alerts such as `> [!WARNING]`, code blocks, and the HTML GitHub allows. Scripts, styles and event handlers are removed. |
989| Secrets | Masked like the log, before the summary leaves the runner. |
990| Size | Up to 1 MiB a step. A larger summary is refused with an error in the step's log, as on GitHub. |
991| Steps | Up to 20 steps of a job keep a summary; later ones are dropped. |
992| Actions | A JavaScript action's `core.summary` writes to the same file, so it works unchanged. |
993
994Summaries belong to their attempt: an earlier attempt keeps its own.
995
996### Re-running
997
998When a run has finished, someone with the Write role can run it again:
999
1000| Button | Runs again |
1001| --- | --- |
1002| **Re-run all jobs** | Every job. |
1003| **Re-run failed jobs** | Jobs that did not succeed (failed, cancelled or skipped), and every job that needs one of them. |
1004| The re-run button beside a job's name | That job, and every job that needs it. A job of a matrix runs again with the rest of its matrix; a job of a reusable workflow runs again with the job that calls it. |
1005
1006Jobs that are not run again keep how they ended, and their outputs reach
1007the jobs that need them.
1008
1009Each re-run is a new **attempt**. The run keeps its number, `github.run_attempt`
1010goes up by one, and the attempt before is kept as it ended: its jobs,
1011their steps, logs and summaries. Pick one from **Attempt #** at the top of
1012the page to read it. Its jobs have ids of their own, so a link to an
1013earlier attempt's job keeps showing that job's log.
1014
1015#### Debug logging
1016
1017Each re-run asks whether to **Enable debug logging**. The new attempt's
1018jobs then run with:
1019
1020| Set | Effect |
1021| --- | --- |
1022| `RUNNER_DEBUG=1`, and `runner.debug` is `1` | Actions that check it, such as the toolkit's `core.isDebug()`, log more. |
1023| `ACTIONS_STEP_DEBUG=true` | `::debug::` lines are shown in the log. |
1024| `ACTIONS_RUNNER_DEBUG=true` | Set for actions that read it. |
1025
1026With debug logging, each step's log also says how its `if:` read:
1027`Evaluating condition for step`, the expression, and the result. Setting a
1028secret or variable named `ACTIONS_STEP_DEBUG` to `true` shows `::debug::`
1029lines on every run instead. The attempt picker marks attempts that ran
1030with debug logging.
1031
1032### Cancelling
1033
1034**Cancel run** cancels jobs that have not started at once. A job that is
1035running is stopped the way GitHub stops one:
1036
10371. The step it is on gets `SIGINT`, then `SIGTERM` 7.5 seconds later, and
1038 is killed 2.5 seconds after that. Signals reach the processes the step
1039 started too; in a [job container](#job-containers) they reach only the
1040 `docker exec` that runs the step. The step ends **cancelled**.
10412. Its remaining steps run only if they ask to: `if: always()` or
1042 `if: cancelled()`. Steps without an `if:`, or with `success()` or
1043 `failure()`, are skipped.
10443. Post steps (an action's `post`, saving the cache) run, as their
1045 `post-if` is `always()` unless the action says otherwise.
10464. The job ends **cancelled**, whatever those steps came to.
1047
1048While that happens the run says **Cancelling**. A job still going 5
1049minutes after it was cancelled is stopped outright. **Force cancel**
1050(shown while a run is cancelling) stops every job at once, without
1051waiting for its cleanup steps.
1052
1053A step learns of a cancellation within about 10 seconds, even when it
1054prints nothing. A job on a [self-hosted runner](/guides/self-hosted-runners/)
1055is stopped the same way.
1056
1057### Searching and downloading logs
1058
1059The **Search logs** box above a job's steps shows only the lines that hold
1060what you type, in any case, with each match marked and every step that has
1061one opened. Lines inside folded groups are searched too.
1062
1063| Download | Where |
1064| --- | --- |
1065| One job's whole log, as text | The download button beside the job's name. |
1066| Every job's log of an attempt, as a zip | **Download logs** at the top of the run. The zip holds `1_<job>.txt` with each job's whole log, and a `<job>/` folder with `<step>_<step name>.txt` for each step. |
1067
1068A zip holds up to 24 MiB of logs; jobs past that are listed with a note
1069to download them on their own. Each job keeps up to 4 MB of log.
1070
1071### Status badges
1072
1073A badge shows how a workflow's latest finished run went: **passing**,
1074**failing**, **cancelled**, or **no status** before it has finished one.
1075
10761. Open the repository's **Actions** page and pick the workflow.
10772. Click **Create status badge**.
10783. Choose a branch and an event, if you want them, and copy the Markdown.
1079
1080```markdown
1081[![CI](https://g1t.sh/acme/web/actions/workflows/ci.yml/badge.svg)](https://g1t.sh/acme/web/actions?workflow=ci.yml)
1082```
1083
1084The address is `https://g1t.sh/{workspace}/{repo}/actions/workflows/{file}/badge.svg`,
1085where `{file}` is the workflow's file name in `.g1t/workflows/`. It takes:
1086
1087| Parameter | Shows |
1088| --- | --- |
1089| `branch` | Runs on that branch. Without it, the default branch's runs, or any branch's when the default branch has none. |
1090| `event` | Runs started by that event, such as `push` or `pull_request`. |
1091
1092A public repository's badge loads for anyone and is cached for a minute.
1093A private repository's loads only for someone who can see the repository,
1094so it does not show in a README read anywhere else.
1095
1096### Masking secrets
1097
1098Every secret's value is replaced with `***` wherever a job prints it, and
1099so is every value a step masks with `::add-mask::`. Each is masked in the
1100forms it shows up in:
1101
1102| Form | Example |
1103| --- | --- |
1104| As it is | `echo $API_KEY` |
1105| Each of its lines on its own | `cat key.pem`, which prints a private key a line at a time |
1106| Base64 | `echo -n $API_KEY \| base64`, or an `Authorization: Basic` header |
1107| JSON-escaped | A secret holding quotes or newlines printed inside JSON |
1108
1109Annotations' titles and messages, and step names, are masked the same way.
1110A job output that holds a secret in any of those forms is left out, with
1111a warning in the log, since outputs go to other jobs and to the run's page.
1112A value of one character is not masked: it would hide that character
1113everywhere.
1114
1115## Pull requests
1116
1117A pull request's workflows run on each new head: when it is opened, when
1118a commit is pushed to it, and, for one g1t makes, when g1t
1119marks it ready, which on g1t is when it first has code. Each head runs
1120each workflow once.
1121
1122They also start on the activity types `reopened` (also run by
1123default, as `opened` and `synchronize` are), `converted_to_draft`,
1124`ready_for_review`, `labeled`, `unlabeled`, `milestoned`, `demilestoned`,
1125`assigned`, `review_requested` and `closed`, and `edited` when the branch
1126a pull request merges into changes; `issue_comment` workflows on
1127`created`, `edited` (with `github.event.changes.body.from`) and `deleted`; `issues` workflows on `labeled`, `unlabeled`, `milestoned` and
1128`demilestoned` too. List them under `types:` to run on them. For
1129`labeled` and `unlabeled`, `github.event.label` names the label. A pull
1130request's `branches` filter, `github.base_ref` and
1131`pull_request.base.ref` are the branch it merges into, which is not
1132always the default branch: see
1133[pull requests into other branches](/guides/base-branches/).
1134
1135A pull request from outside the workspace may wait for approval before
1136its workflows run: see [pull requests from outside](#pull-requests-from-outside).
1137
1138`github.event.pull_request` reads as it does on GitHub. For a pull request
1139g1t made, `pull_request.user` is g1t (`login` `g1t`, `type` `Bot`), and
1140`pull_request.requested_by` names the person who asked for it; it is `null`
1141on anyone else's. `github.event.issue.requested_by` does the same for an
1142issue g1t's agent filed. `sender` is whoever caused the event.
1143
1144## Checks
1145
1146A pull request's checks are its workflows. Each workflow that runs on
1147`pull_request` runs on every pull request's head, whoever opened it, a
1148person or an agent, and its runs report a check named after the workflow:
1149a workflow with `name: CI` reports `CI`, with the status context
1150`CI / pull_request` (the workflow's name and the event). Each of its jobs
1151is a [check run](/guides/checks/) on the commit, shown as
1152`CI / test (pull_request)` beside it wherever it appears.
1153
1154- **Which checks a merge needs** is up to the [rules](/guides/rules/) of the branch it merges into,
1155 their [required status checks](/guides/pull-requests/#required-status-checks),
1156 under **Settings → Rules**. A required check that failed,
1157 is still running or has not reported holds the merge. Checks that are not
1158 required are shown on the pull request and never hold it.
1159- **In a repository that merges through the [merge queue](/guides/merge-queue/)**,
1160 workflows with `on: merge_group` run on each combined state the queue
1161 builds, on the branch `g1t-queue/<entry>`, and the state lands only if
1162 they and every required check pass on it. A workflow behind a required
1163 check needs `merge_group` in its `on:`.
1164- **A pull request g1t is working on** goes back to g1t when
1165 a check fails, with the end of each failed job's log. The agent reads the
1166 run and its logs with the same tools you have, fixes the cause, and
1167 pushes; the workflows run again. See
1168 [seeing it through](/guides/working-with-g1t/#seeing-it-through).
1169
1170```yaml
1171name: CI
1172
1173on:
1174 pull_request:
1175 push:
1176 branches: [main]
1177 merge_group:
1178```
1179
1180### Add CI
1181
1182A repository with no workflows has nothing that proves a change works, for
1183people or for agents. Its pull requests, its **Branches and merging**
1184settings and its **Actions** page say **This repository has no checks**,
1185with an **Add CI** button. Anyone who can push to the repository can use it:
1186
11871. Choose **Add CI**. g1t looks at the files at the repository's root and
1188 writes a starter workflow with a job for each stack it finds, up to
1189 three: Node (npm, pnpm, Yarn or Bun), Rust, Go, Python (pip or uv), Ruby,
1190 Java (Maven or Gradle), .NET, or Make. Each job installs, lints where
1191 the project says how, builds and tests. When it finds none, the job is a
1192 placeholder that fails until you replace its last step with your own
1193 commands.
11942. The workflow is committed as `.g1t/workflows/ci.yml` on a new branch,
1195 `add-ci`, and opened as a pull request, by you. It is named `CI` and runs
1196 on `pull_request`, on `push` to the default branch, and on `merge_group`.
11973. Change it on the pull request if the steps are not how your project
1198 builds, and merge it.
11994. Once it has run, `CI` is offered under **Require status checks to pass
1200 before merging**.
1201 Require it, so that nothing merges into the default branch unless it
1202 passes.
1203
1204## Secrets and variables
1205
1206Secrets are read as `${{ secrets.KEY }}` and config as `${{ vars.KEY }}`,
1207from the rows under **Settings → Secrets and variables** that are
1208available to Workflows. A job with `environment: production` reads each
1209key's Production row; other jobs read the rows for all environments. A
1210job with an `environment:` also makes a deployment to it; see
1211[deployments from g1t Actions](/guides/deployments-api/#deployments-from-g1t-actions). See
1212[Secrets and variables](/guides/secrets-and-variables/) for how rows,
1213environments and the workspace's rows work.
1214
1215Every job also gets `${{ secrets.G1T_TOKEN }}`, [its own token](#the-jobs-token),
1216with `GITHUB_TOKEN` as its alias. A pull request's runs get secrets only
1217when its author has the Write [role](/guides/access-and-roles/) or higher
1218on the repository, a member or an outside collaborator, or is g1t working
1219on its own. For a pull request g1t made, its author is g1t and the person
1220who asked for it is the one whose role counts. Anyone else's, such as one
1221from a fork or by someone with Read or Triage, runs without secrets and
1222with a token that can only read. See
1223[who gets secrets](/guides/secrets-and-variables/#who-gets-secrets).
1224
1225## The job's token
1226
1227Each job gets a token of its own, `${{ secrets.G1T_TOKEN }}`
1228(`${{ secrets.GITHUB_TOKEN }}` and `${{ github.token }}` are the same).
1229`actions/checkout` uses it, and so can any step that calls the
1230[API](/reference/api/) or pushes with git:
1231
1232- It reaches **this repository only**. Every other repository, even one
1233 in the same workspace, is refused. So are packages: it reaches this
1234 repository's own, and another package only once the package's admins
1235 add this repository under its
1236 [Manage Actions access](/guides/packages/#manage-actions-access).
1237- It can do **what its `permissions:` say**, and nothing more.
1238- It **stops working when the job ends**, however it ends.
1239- Everything it changes is in the [audit log](/guides/audit-log/) as that
1240 job's, under its run.
1241- What it changes **starts no workflows**: a push, a pull request, an issue
1242 or a comment made with it runs nothing, so a workflow cannot set itself
1243 off. `workflow_dispatch` and [`repository_dispatch`](#repository-dispatch)
1244 are the exceptions, for a workflow that means to start another.
1245- It **never puts g1t to work**. A comment it posts that mentions
1246 `@g1t` starts nothing, and it cannot assign an issue or a plan to g1t,
1247 queue one for it, hand it work or ask it for a review. Otherwise a
1248 workflow that asks g1t to fix a failing check would run again on g1t's
1249 push, and ask again, without end. A step that should put g1t to work
1250 uses a token of a person's own, stored as a
1251 [secret](/guides/secrets-and-variables/).
1252
1253`permissions:` goes at the top of the workflow, for every job, or on a job,
1254which then ignores the workflow's. Once either is written, every permission
1255it leaves out is `none`:
1256
1257```yaml
1258permissions:
1259 contents: read
1260
1261jobs:
1262 release:
1263 runs-on: ubuntu-latest
1264 permissions:
1265 contents: write
1266 pull-requests: write
1267 steps:
1268 - uses: actions/checkout@v5
1269 - run: ./scripts/release.sh
1270```
1271
1272| Permission | `read` lets it | `write` also lets it |
1273| --- | --- | --- |
1274| `contents` | Clone and fetch with git, read the repository | Push, publish releases |
1275| `pull-requests` | Read pull requests | Open, review, close and merge them |
1276| `issues` | Read issues | Open, edit, comment on and close them |
1277| `actions` | Read workflows, runs and logs | Run, cancel and re-run them |
1278| `checks`, `statuses` | Read statuses and check runs | Report them |
1279| `deployments`, `pages` | Read deployments | Report them |
1280| `packages` | Pull packages | Push and publish them |
1281| `security-events` | Read security alerts | Upload code scanning results, change alerts |
1282| `metadata` | Always `read` | |
1283| `id-token` | Nothing | Ask for an [OIDC token](#oidc-tokens) |
1284| `discussions`, `attestations`, `models`, `repository-projects` | Nothing on g1t | Nothing on g1t |
1285
1286`read-all` and `write-all` set every permission; `permissions: {}` sets
1287none, so the token cannot even clone a private repository. A reusable
1288workflow's jobs get no more than the job that calls it.
1289
1290There is no `workflows` permission for a job's token: it can never add,
1291change or delete a file under `.g1t/workflows/` or `.github/workflows/`,
1292even with `contents: write`. A push that does is declined, naming the file,
1293so a workflow cannot rewrite the workflows that run with its repository's
1294secrets. To change workflows from a job, push with a
1295[personal access token](/guides/authentication/#workflow-files) that has
1296Workflow files: write, kept as a secret.
1297
1298The job's token is the repository's workspace acting with the Write role
1299at most, never Admin: it cannot manage webhooks, secrets, deploy keys or who
1300has access, whatever it asks for.
1301
1302**Without `permissions:`** a job gets the repository's default, which
1303someone with the Admin role sets under **Settings → Actions**:
1304
1305| Repository | Default until someone chooses |
1306| --- | --- |
1307| Made before restricted tokens came in, in October 2026 | **Read and write**: every permission at `write`, as before |
1308| Made since | The workspace's default for new repositories: **Read repository contents and packages** (`contents: read`, `packages: read`) unless an owner chose otherwise |
1309
1310The workspace's owners set that default, and a **maximum**, under the
1311workspace's **Settings → Actions**: with a maximum of **Read only**, no
1312repository's default goes past `contents: read` and `packages: read`,
1313whatever it chose. Workflows that write `permissions:` get what they
1314write either way, and whatever a workflow asks for, a pull request from
1315outside the repository's writers (a fork, or someone with Read or Triage)
1316gets a token that can only read.
1317
1318**Allow g1t Actions to create and approve pull requests** is off unless a
1319repository's admin turns it on under **Settings → Actions**, and they can
1320only where the workspace's owners allow it. Until then a job's token
1321cannot open a pull request or approve one, whatever its `pull-requests`
1322permission says; it can still read, comment on, review with changes
1323requested, and merge them.
1324
1325The token can never change secrets, variables, environments' rules or
1326the Actions settings, approve runs or deployments, or reach another
1327repository.
1328
1329## Environments
1330
1331A job that names an environment with `environment:` reads that
1332environment's [secrets and variables](/guides/secrets-and-variables/#a-value-per-environment)
1333and records a [deployment](/guides/deployments-api/#deployments-from-g1t-actions)
1334to it. Give the environment protection rules, and such a job waits until
1335they let it through, and only then gets the environment's secrets:
1336
1337| Rule | What it does |
1338| --- | --- |
1339| **Required reviewers** | Up to 6 people or teams. The job waits until one of them approves it. |
1340| **Prevent self-review** | Whoever started the run cannot approve it, even as a reviewer. |
1341| **Wait timer** | Minutes the job waits once it reaches the environment, up to 43,200 (30 days). |
1342| **Deployment branches and tags** | **All branches**; **Protected branches only**, those the repository's [rules](/guides/rules/) protect, the default branch included; or **Selected branches and tags**, by pattern, such as `main`, `release/*` or `v*`. A job on any other ref fails, saying so. A pull request's run is on no branch, so it can deploy only where all branches may. |
1343| **Allow admins to bypass** | On unless you turn it off: someone with the Admin role may approve without being a reviewer, which also skips the wait timer. |
1344
1345To set them:
1346
13471. Open the repository's **Settings → Environments**. It lists every
1348 environment your workflows, secrets and deployments name.
13492. Choose one, or name a new one, and set its rules.
13503. Save. Runs that reach the environment from then on wait by them.
1351
1352A run whose jobs wait shows **Waiting for review** at the top of its page,
1353with the environments, the jobs each holds, its reviewers and when its
1354wait timer runs out. Reviewers are told in their [notifications](/guides/notifications/);
1355on the run's page they choose **Approve and deploy** or **Reject**, with
1356room for a comment. One review covers every job of the run that names the
1357environment. A rejected job fails, and so does anything that needs it. The
1358rest of the run goes on meanwhile: jobs that do not need the waiting ones
1359run.
1360
1361The environment's name may be an expression, such as
1362`environment: ${{ inputs.target }}`: it is read once the job's needs are
1363done, and its rules and secrets are that environment's. Names are matched
1364without regard to case.
1365
1366From the API, `PUT /repos/{owner}/{repo}/environments/{environment}` sets
1367the rules, with `reviewers`, `prevent_self_review`, `wait_timer`,
1368`deployment_branch_policy`, `branch_policies` and `can_admins_bypass`;
1369`GET` on the same route returns them as `protection_rules`; `DELETE`
1370removes them. `POST /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments`
1371approves or rejects a run's waiting jobs:
1372
1373```sh
1374curl -X PUT https://api.g1t.sh/repos/acme/web/environments/production \
1375 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
1376 -d '{"reviewers": [{"type": "Team", "name": "deployers"}], "wait_timer": 10,
1377 "deployment_branch_policy": {"protected_branches": true, "custom_branch_policies": false}}'
1378```
1379
1380A job's own token cannot approve, reject or change any of it.
1381
1382## Pull requests from outside
1383
1384A pull request from someone outside the workspace runs code anyone could
1385have written. By the repository's **approval policy**, its runs wait as
1386**Approval required** until someone with the Write
1387[role](/guides/access-and-roles/) chooses **Approve and run** on the run's
1388page. Nothing runs before then: no job starts, and no token or secret is
1389handed out.
1390
1391| Policy, under **Settings → Actions** | Whose pull requests' runs wait |
1392| --- | --- |
1393| **First-time contributors** | Someone outside the workspace who has not had a pull request merged here yet. |
1394| **Outside contributors** (the default) | Those, and everyone outside the workspace who cannot push here: pull requests from forks, and from people with Read or Triage. |
1395| **All external contributors** | Everyone outside the workspace, [outside collaborators](/guides/access-and-roles/#outside-collaborators) with Write included. |
1396
1397Members' pull requests never wait, nor do pull requests g1t opens on its
1398own. For a pull request g1t made for someone, that person is the one whose
1399policy counts. Each new push to the pull request waits again.
1400`pull_request_target` runs, which run the default branch's workflow and
1401code, never wait.
1402
1403From the API: `POST /repos/{owner}/{repo}/actions/runs/{id}/approve`
1404approves a run, and `GET` and `PUT
1405/repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval`
1406read and set the policy, as `approval_policy`.
1407
1408## Repository dispatch
1409
1410`POST /repos/{owner}/{repo}/dispatches` starts the default branch's
1411workflows that run `on: repository_dispatch` for its `event_type`, those
1412listing it under `types:` or listing none. `client_payload` is theirs to
1413read as `github.event.client_payload`:
1414
1415```yaml
1416on:
1417 repository_dispatch:
1418 types: [docs-published]
1419
1420jobs:
1421 announce:
1422 runs-on: ubuntu-latest
1423 steps:
1424 - run: echo "Docs ${{ github.event.client_payload.version }} are out"
1425```
1426
1427```sh
1428curl -X POST https://api.g1t.sh/repos/acme/web/dispatches \
1429 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
1430 -d '{"event_type": "docs-published", "client_payload": {"version": "2.4.0"}}'
1431```
1432
1433It needs the Write role, or a token with `code:write`; a job's own token
1434needs `contents: write`. `client_payload` is a JSON object of at most 10
1435properties and 64 KB.
1436
1437## Who may run workflows
1438
1439What you can do with a repository's workflows follows your
1440[role](/guides/access-and-roles/) on it:
1441
1442| | Needs |
1443| --- | --- |
1444| See workflows, runs and their logs | Read: on a public repository, anyone |
1445| Run a workflow by hand, cancel or re-run a run, approve a pull request's run from outside | Write |
1446| Approve or reject a job waiting for an environment | One of the environment's reviewers |
1447| Enable or disable a workflow | Maintain |
1448| The repository's secrets and variables, seeing them included; environments' rules; **Settings → Actions** | Admin |
1449
1450Jobs run in g1t's sandboxes, so they need the
1451[g1t plan](/guides/usage-and-billing/#the-g1t-plan) or
1452[the trial](/guides/usage-and-billing/#the-trial); jobs on
1453[self-hosted runners](/guides/self-hosted-runners/#billing) need neither.
1454On a public repository,
1455[g1t's open-source pool](/guides/usage-and-billing/#the-open-source-pool)
1456runs them too, after a card check, until the month's pool is spent.
1457
1458Before each job starts, g1t reserves what it may cost (its time limit at
1459the sandbox price) with billing, and settles what it really cost when it
1460ends; each job's sandbox is charged as
1461[sandbox time](/guides/usage-and-billing/#sandbox-time), from the first
1462second. A job billing refuses does not start: it is recorded as failed
1463with "Not started:" and the reason, such as "Workflows run in g1t's
1464sandboxes, which cost real money, so they need the g1t plan ($20 a month)
1465or a card check", and what to do about it. The
1466Actions page tells people with Write on a repository whose workspace
1467cannot run jobs before the first run.
1468
1469## From the API
1470
1471The routes follow the standard Actions REST shape, so existing scripts
1472usually work once they point at `https://api.g1t.sh`.
1473
1474| `workflow` action | Route |
1475| --- | --- |
1476| `list` | `GET /repos/{owner}/{repo}/actions/workflows` |
1477| `list_runs` | `GET /repos/{owner}/{repo}/actions/runs`, with `workflow`, `branch`, `event`, `pull`, `head_sha` |
1478| `get_run` | `GET /repos/{owner}/{repo}/actions/runs/{id}` |
1479| `job_logs` | `GET /repos/{owner}/{repo}/actions/jobs/{job}/logs?after=`, or `?format=text` for the whole log as plain text |
1480| `get_run` with `attempt` | `GET /repos/{owner}/{repo}/actions/runs/{id}/attempts/{attempt}` |
1481| No tool: a download | `GET /repos/{owner}/{repo}/actions/runs/{id}/logs`, or `…/attempts/{attempt}/logs`: every job's log as a zip |
1482| `dispatch` | `POST /repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches` with `ref` and `inputs` |
1483| `cancel` | `POST /repos/{owner}/{repo}/actions/runs/{id}/cancel`; `…/force-cancel`, or `force`, to stop running jobs without their cleanup steps |
1484| `rerun` | `POST …/runs/{id}/rerun`, or `…/rerun-failed-jobs`; one job and those that need it with `POST /repos/{owner}/{repo}/actions/jobs/{job}/rerun`. Each takes `enable_debug_logging` (or `debug`) |
1485| `update` | `PUT …/workflows/{workflow}/enable` and `…/disable` |
1486| `approve_run` | `POST /repos/{owner}/{repo}/actions/runs/{id}/approve` |
1487| `pending_deployments` | `GET /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments` |
1488| `review_deployments` | `POST /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments` with `environment_names`, `state` and `comment` |
1489| `get_environment` | `GET /repos/{owner}/{repo}/environments/{environment}` |
1490| `update_environment` | `PUT /repos/{owner}/{repo}/environments/{environment}` |
1491| `delete_environment` | `DELETE /repos/{owner}/{repo}/environments/{environment}` |
1492| `get_permissions`, `set_permissions` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/workflow`, with `default_workflow_permissions` (`read`, `write` or `inherit`) and `can_approve_pull_request_reviews` |
1493| `get_workspace_permissions`, `set_workspace_permissions` | `GET` and `PUT /workspaces/{workspace}/actions/permissions/workflow`, with `default_workflow_permissions`, `max_workflow_permissions` and `can_approve_pull_request_reviews` |
1494| `get_approval_policy`, `set_approval_policy` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval` |
1495| `get_access`, `set_access` | `GET` and `PUT /repos/{owner}/{repo}/actions/permissions/access`, with `access_level` (`none` or `organization`) |
1496| `repository_dispatch` | `POST /repos/{owner}/{repo}/dispatches` with `event_type` and `client_payload` |
1497| `list_artifacts` | `GET /repos/{owner}/{repo}/actions/artifacts`, with `name`, `page`, `per_page` |
1498| `run_artifacts` | `GET …/actions/runs/{id}/artifacts`, with `name` |
1499| `get_artifact` | `GET …/actions/artifacts/{artifact_id}` |
1500| `download_artifact` | `GET …/actions/artifacts/{artifact_id}/zip`: a `302` to a link good for 10 minutes |
1501| `delete_artifact` | `DELETE …/actions/artifacts/{artifact_id}` |
1502| `artifact_retention`, `set_artifact_retention` | `GET` and `PUT …/actions/permissions/artifact-and-log-retention` with `days` (it sets artifacts' days only; logs are kept with their run) |
1503
1504Secrets and variables have a tool of their own, `secret`:
1505
1506| `secret` action | Route |
1507| --- | --- |
1508| `list_secrets`, `set_secret`, `delete_secret` | `GET /repos/{owner}/{repo}/actions/secrets`, `PUT` and `DELETE …/secrets/{name}` |
1509| `list_variables`, `set_variable`, `delete_variable` | `GET` and `POST /repos/{owner}/{repo}/actions/variables`, `PATCH` and `DELETE …/variables/{name}` |
1510
1511Workspace secrets and variables are under
1512`/workspaces/{workspace}/actions/secrets` and `…/variables`. The fields
1513g1t adds (environments, who reads a row, linked repositories) are in
1514[Secrets and variables](/guides/secrets-and-variables/#from-the-api).
1515
1516```sh
1517curl -X POST https://api.g1t.sh/repos/acme/web/actions/workflows/ci.yml/dispatches \
1518 -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \
1519 -d '{"ref": "main", "inputs": {"environment": "staging"}}'
1520```
1521
1522To save an artifact from a script, follow the redirect:
1523
1524```sh
1525curl -L -o web-dist.zip -H "Authorization: Bearer $G1T_TOKEN" \
1526 https://api.g1t.sh/repos/acme/web/actions/artifacts/4182/zip
1527```