Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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