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