| 1 | --- |
| 2 | title: GitHub Actions |
| 3 | description: Your GitHub Actions workflows run on g1t as they are. Rename .github to .g1t and push. |
| 4 | --- |
| 5 | |
| 6 | g1t runs GitHub Actions workflows. They are written exactly as on GitHub, |
| 7 | and kept in `.g1t/workflows/` instead of `.github/workflows/`. |
| 8 | |
| 9 | ## Moving from GitHub |
| 10 | |
| 11 | ```sh |
| 12 | git mv .github .g1t |
| 13 | git commit -m "Run our workflows on g1t" |
| 14 | git push g1t main |
| 15 | ``` |
| 16 | |
| 17 | That is the whole move. Everything in the folder comes along: workflows, |
| 18 | local actions under `.g1t/actions/` and anything else you keep there. |
| 19 | Workflows that still say `uses: ./.github/actions/setup` find it under |
| 20 | `.g1t/` once `.github` is gone. |
| 21 | |
| 22 | g1t never reads `.github`. A repository mirrored to both places can keep |
| 23 | `.github` for GitHub and `.g1t` for g1t, side by side. |
| 24 | |
| 25 | Then add your [secrets and variables](#secrets-and-variables): GitHub never |
| 26 | gives 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 | |
| 58 | The **Actions** page of a workflow says, under *How this runs on g1t*, |
| 59 | anything 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 | |
| 78 | Why 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 | |
| 83 | A 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. |
| 85 | g1t 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 |
| 96 | also finds the file under `.g1t/workflows/` in a repository moved to |
| 97 | g1t. A `./.g1t/workflows/…` call inside a workflow from another |
| 98 | repository reads from that repository, at the same ref. |
| 99 | |
| 100 | To share a private repository's actions and workflows with the rest of its |
| 101 | workspace, an admin chooses **Settings → Actions → Access → Accessible from |
| 102 | repositories 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 | |
| 108 | A called workflow gets only the secrets its caller passes, plus |
| 109 | `G1T_TOKEN` (`GITHUB_TOKEN`): |
| 110 | |
| 111 | ```yaml |
| 112 | jobs: |
| 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 |
| 136 | with the calling run's `github` context: `actions/checkout` checks out the |
| 137 | calling repository. |
| 138 | |
| 139 | ## Releases |
| 140 | |
| 141 | Workflows with `on: release` start when a release changes, at the commit |
| 142 | its tag names (`GITHUB_REF` is `refs/tags/<tag>`). Each change is one or |
| 143 | more 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 |
| 156 | on: |
| 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 |
| 163 | job'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 |
| 169 | the [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 |
| 174 | on: deployment_status |
| 175 | |
| 176 | jobs: |
| 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` |
| 186 | and `log_url`. Deployments a workflow makes, with `environment:` or with |
| 187 | its job's token, start no workflows, so a workflow cannot set itself off. |
| 188 | |
| 189 | ## The runner |
| 190 | |
| 191 | Jobs run in a fresh sandbox each: Debian with Node 24, Python 3, Go, Rust, |
| 192 | Java 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 |
| 196 | labels all run here. A job whose `runs-on` names `self-hosted` waits for one |
| 197 | of 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 |
| 199 | they do on GitHub. |
| 200 | |
| 201 | ### Languages and their setup actions |
| 202 | |
| 203 | Each language below is on `PATH` from the job's first step, so a workflow |
| 204 | that only runs `java`, `dotnet` or `ruby` needs no setup step. When it has |
| 205 | one, the setup action finds the version that is already there and |
| 206 | downloads 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 |
| 219 | steps: |
| 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 | |
| 228 | Because the sandbox runs Debian, `ruby/setup-ruby` treats it as a |
| 229 | self-hosted runner and uses only the Rubies in `RUNNER_TOOL_CACHE`. A version other than 3.3 fails at that step; install |
| 230 | it in a `run` step instead (for example with `ruby-build`) or run the job |
| 231 | in a `container:` with the Ruby you need, such as `ruby:3.4`. |
| 232 | |
| 233 | The 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 | |
| 238 | The `gh` command is not installed: it needs a GraphQL API, and g1t's API |
| 239 | is REST. Call it with `curl`, using the job's token and the API's address, |
| 240 | which 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 | |
| 252 | The token reaches this repository and does what the job's `permissions:` |
| 253 | say; see [the job's token](#the-jobs-token). The |
| 254 | [API reference](/reference/api/) lists every endpoint. |
| 255 | |
| 256 | ### Machine sizes |
| 257 | |
| 258 | A job runs on the standard machine unless its `runs-on` names a larger |
| 259 | one: |
| 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 |
| 268 | jobs: |
| 269 | build: |
| 270 | runs-on: g1t-4core |
| 271 | ``` |
| 272 | |
| 273 | The label can come from the matrix or the run's inputs |
| 274 | (`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). A |
| 275 | larger machine costs what it costs g1t, plus the same margin as all |
| 276 | sandbox time: see [usage and billing](/guides/usage-and-billing/#workflow-jobs-on-larger-machines). |
| 277 | Builds that compile, such as Rust or a large TypeScript project, finish |
| 278 | several times faster on one. |
| 279 | |
| 280 | A 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. |
| 282 | A job stopped at its time cap fails saying so. |
| 283 | |
| 284 | ### What a job can reach |
| 285 | |
| 286 | A job's network is restricted, as an agent's is (see |
| 287 | [guardrails](/guides/guardrails/)): it reaches the hosts its project's |
| 288 | guardrails allow, g1t itself, and what builds need, and nothing else. |
| 289 | What builds need is the package registries (npm, PyPI, crates.io, the Go |
| 290 | proxy, RubyGems, Packagist, NuGet, Maven and Gradle, Debian's mirrors), |
| 291 | GitHub, where `uses:` actions and the setup actions' downloads come from, |
| 292 | the toolchains' download sites (`nodejs.org`, `go.dev`, |
| 293 | `static.rust-lang.org`), and the public container registries (Docker Hub, |
| 294 | GitHub's, Quay, and `mirror.gcr.io`, the mirror of Docker Hub that a job's |
| 295 | Engine asks first). A request anywhere else gets `403` with |
| 296 | the reason. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or |
| 297 | higher adds it to the project's |
| 298 | allowed domains under **Settings → Guardrails**; a project whose guardrails |
| 299 | turn the network restriction off runs its jobs with an open network. |
| 300 | |
| 301 | A host only workflows should reach, such as the API a deploy uploads to, |
| 302 | goes in **Workflow-only domains** instead, limited to the workflows and |
| 303 | environments that need it: `api.cloudflare.com | deploy.yml | production` |
| 304 | lets only `deploy.yml`'s jobs with `environment: production` reach it. |
| 305 | Agents never reach those hosts, and neither do runs of pull requests from |
| 306 | forks. See [workflow-only domains](/guides/guardrails/#workflow-only-domains). |
| 307 | |
| 308 | g1t does not run cryptocurrency miners: a step that names one (`xmrig`, |
| 309 | a `stratum+tcp://` pool, `--donate-level`) is not run, and a job that |
| 310 | looks like it is mining is stopped. See |
| 311 | [abuse and mining](/guides/guardrails/#abuse-and-mining). |
| 312 | |
| 313 | ## Docker |
| 314 | |
| 315 | Each job on g1t's machines has a Docker Engine of its own, inside the |
| 316 | job's sandbox. Nothing runs until the job uses it: the first `docker` |
| 317 | command, or a job's `services:` or `container:`, starts it, in a second |
| 318 | or two, and the log says so. It ends with the job, with every image, |
| 319 | container and build cache in it. No other job, repository or workspace |
| 320 | ever shares it. |
| 321 | |
| 322 | ```yaml |
| 323 | jobs: |
| 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 | |
| 357 | Every 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 | |
| 375 | A container that asks for `--network none` gets none, and |
| 376 | `--network container:<name>` shares that container's. |
| 377 | |
| 378 | ### Job containers |
| 379 | |
| 380 | With `container:`, the steps run inside the image as its default user, |
| 381 | usually `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 | |
| 396 | On g1t's machines, a job is signed in to g1t's container registry from |
| 397 | the start, with its own `G1T_TOKEN`, so it can push to and pull from its |
| 398 | repository's images without a login step; images of other repositories |
| 399 | need this one added under their |
| 400 | [Manage Actions access](/guides/packages/#manage-actions-access). A run that gets no secrets is |
| 401 | not signed in. See [container registry](/guides/containers/#in-workflows). |
| 402 | |
| 403 | ```yaml |
| 404 | jobs: |
| 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 | |
| 418 | For other registries, sign in with `docker/login-action` or |
| 419 | `docker login`, as on GitHub. Docker Hub's images are pulled through its |
| 420 | public mirror first, so jobs are rarely held up by Docker Hub's limits on |
| 421 | anonymous pulls. |
| 422 | |
| 423 | #### Caching image builds |
| 424 | |
| 425 | The Engine starts empty in every job, so a build's layers are rebuilt |
| 426 | unless 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 | |
| 486 | So a feature branch can read what `main` saved, but `main` never reads |
| 487 | what a feature branch saved, and a pull request from outside the |
| 488 | repository saves where nothing else ever reads it: nobody can plant an |
| 489 | entry that the default branch's builds restore. Actions that cache through the |
| 490 | toolkit, such as `actions/setup-node` with `cache: npm`, follow the same |
| 491 | rules. |
| 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 | |
| 504 | Each restore and save says on the job's log how large the entry was and |
| 505 | how long it took. A workspace on the plan pays for what its caches and |
| 506 | [artifacts](#artifacts) hold (`Actions cache storage` on its statement), |
| 507 | at 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 |
| 513 | jobs, 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 |
| 537 | artifact of the run, each into a folder of its name; `pattern` picks them |
| 538 | by 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 |
| 540 | downloads 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 |
| 553 | you say), and deletes them with `delete-merged: true`. |
| 554 | |
| 555 | A run's page lists its artifacts with their size and when they expire. |
| 556 | Anyone who can see the run downloads them there; someone with the Write |
| 557 | role can delete one before it expires. |
| 558 | |
| 559 | ## Actions built on the toolkit |
| 560 | |
| 561 | Many actions save to the cache with GitHub's toolkit, `@actions/cache`, |
| 562 | rather 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 |
| 565 | are: every job gets `ACTIONS_RUNTIME_TOKEN`, `ACTIONS_CACHE_URL` and |
| 566 | `ACTIONS_RESULTS_URL`, and g1t answers the toolkit's requests from the |
| 567 | repository'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 | |
| 586 | A job can prove which repository, branch and environment it runs for with |
| 587 | a short-lived OpenID Connect token signed by g1t, and trade it for a cloud |
| 588 | provider's credentials. Nothing long-lived needs to sit in a secret. |
| 589 | |
| 590 | 1. 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. |
| 593 | 2. Tell your cloud to trust g1t's issuer for your repository (below). |
| 594 | 3. Use the provider's own login action, which asks for the token. |
| 595 | |
| 596 | ```yaml |
| 597 | permissions: |
| 598 | id-token: write |
| 599 | contents: read |
| 600 | |
| 601 | jobs: |
| 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 | |
| 612 | The job gets `ACTIONS_ID_TOKEN_REQUEST_URL` and |
| 613 | `ACTIONS_ID_TOKEN_REQUEST_TOKEN`, which `core.getIDToken()` reads. To ask |
| 614 | for a token yourself, name its audience: |
| 615 | |
| 616 | ```sh |
| 617 | curl -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 | |
| 631 | Each token carries the claims GitHub's do, so trust policies written for |
| 632 | those 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 | |
| 653 | 1. 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 | |
| 663 | 2. 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 | |
| 682 | 3. Use `aws-actions/configure-aws-credentials@v4` with `role-to-assume`, |
| 683 | as above. |
| 684 | |
| 685 | ### Google Cloud |
| 686 | |
| 687 | 1. 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 | |
| 698 | 2. 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 | |
| 706 | 3. 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 | |
| 714 | 1. 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 | |
| 729 | 2. Give it a role on what it deploys to, as for any identity. |
| 730 | 3. Use `azure/login@v2` with `client-id`, `tenant-id` and |
| 731 | `subscription-id`, and no secret. |
| 732 | |
| 733 | A federated credential matches the subject exactly: add one per |
| 734 | environment or branch that deploys. |
| 735 | |
| 736 | ### Cloudflare |
| 737 | |
| 738 | Cloudflare's API takes API tokens, not OIDC tokens. Keep a token scoped |
| 739 | to what the workflow deploys in a secret, available to that workflow only |
| 740 | (see [workflow-only domains](/guides/guardrails/#workflow-only-domains) |
| 741 | for limiting where it can be sent). |
| 742 | |
| 743 | A Worker of your own can trust g1t's jobs directly, by checking the token |
| 744 | a job sends it against g1t's keys: |
| 745 | |
| 746 | ```ts |
| 747 | import { createRemoteJWKSet, jwtVerify } from "jose"; |
| 748 | |
| 749 | const keys = createRemoteJWKSet(new URL("https://api.g1t.sh/actions/oidc/.well-known/jwks")); |
| 750 | |
| 751 | export 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 | |
| 763 | npm's trusted publishing and provenance accept OIDC tokens only from the |
| 764 | CI services npm lists, and g1t is not one of them yet. Publish with a |
| 765 | granular 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 | |
| 777 | See [What g1t can't do yet](/about/limitations/#no-npm-trusted-publishing-or-provenance). |
| 778 | |
| 779 | ## Runs and logs |
| 780 | |
| 781 | Open a repository's **Actions** page, in its sidebar. Pick a workflow to |
| 782 | see its runs, run it by hand if it has `workflow_dispatch`, or turn it off |
| 783 | without touching its file. |
| 784 | |
| 785 | A run's page shows its jobs, each job's steps, and their logs as they are |
| 786 | written. Groups fold, errors and warnings are marked, and secrets are |
| 787 | replaced with `***`. |
| 788 | |
| 789 | The start of each job's log lists what its [token](#the-jobs-token) may do. |
| 790 | |
| 791 | ### Job summaries |
| 792 | |
| 793 | Markdown a step appends to the file in `$GITHUB_STEP_SUMMARY` shows at |
| 794 | the top of the run's page, a card per job, in the order its steps wrote |
| 795 | it: |
| 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 | |
| 815 | Summaries belong to their attempt: an earlier attempt keeps its own. |
| 816 | |
| 817 | ### Re-running |
| 818 | |
| 819 | When 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 | |
| 827 | Jobs that are not run again keep how they ended, and their outputs reach |
| 828 | the jobs that need them. |
| 829 | |
| 830 | Each re-run is a new **attempt**. The run keeps its number, `github.run_attempt` |
| 831 | goes up by one, and the attempt before is kept as it ended: its jobs, |
| 832 | their steps, logs and summaries. Pick one from **Attempt #** at the top of |
| 833 | the page to read it. Its jobs have ids of their own, so a link to an |
| 834 | earlier attempt's job keeps showing that job's log. |
| 835 | |
| 836 | #### Debug logging |
| 837 | |
| 838 | Each re-run asks whether to **Enable debug logging**. The new attempt's |
| 839 | jobs 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 | |
| 847 | With debug logging, each step's log also says how its `if:` read: |
| 848 | `Evaluating condition for step`, the expression, and the result. Setting a |
| 849 | secret or variable named `ACTIONS_STEP_DEBUG` to `true` shows `::debug::` |
| 850 | lines on every run instead. The attempt picker marks attempts that ran |
| 851 | with debug logging. |
| 852 | |
| 853 | ### Cancelling |
| 854 | |
| 855 | **Cancel run** cancels jobs that have not started at once. A job that is |
| 856 | running is stopped the way GitHub stops one: |
| 857 | |
| 858 | 1. 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**. |
| 862 | 2. 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. |
| 865 | 3. Post steps (an action's `post`, saving the cache) run, as their |
| 866 | `post-if` is `always()` unless the action says otherwise. |
| 867 | 4. The job ends **cancelled**, whatever those steps came to. |
| 868 | |
| 869 | While that happens the run says **Cancelling**. A job still going 5 |
| 870 | minutes after it was cancelled is stopped outright. **Force cancel** |
| 871 | (shown while a run is cancelling) stops every job at once, without |
| 872 | waiting for its cleanup steps. |
| 873 | |
| 874 | A step learns of a cancellation within about 10 seconds, even when it |
| 875 | prints nothing. A job on a [self-hosted runner](/guides/self-hosted-runners/) |
| 876 | is stopped the same way. |
| 877 | |
| 878 | ### Searching and downloading logs |
| 879 | |
| 880 | The **Search logs** box above a job's steps shows only the lines that hold |
| 881 | what you type, in any case, with each match marked and every step that has |
| 882 | one 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 | |
| 889 | A zip holds up to 24 MiB of logs; jobs past that are listed with a note |
| 890 | to download them on their own. Each job keeps up to 4 MB of log. |
| 891 | |
| 892 | ### Status badges |
| 893 | |
| 894 | A badge shows how a workflow's latest finished run went: **passing**, |
| 895 | **failing**, **cancelled**, or **no status** before it has finished one. |
| 896 | |
| 897 | 1. Open the repository's **Actions** page and pick the workflow. |
| 898 | 2. Click **Create status badge**. |
| 899 | 3. Choose a branch and an event, if you want them, and copy the Markdown. |
| 900 | |
| 901 | ```markdown |
| 902 | [](https://g1t.sh/acme/web/actions?workflow=ci.yml) |
| 903 | ``` |
| 904 | |
| 905 | The address is `https://g1t.sh/{workspace}/{repo}/actions/workflows/{file}/badge.svg`, |
| 906 | where `{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 | |
| 913 | A public repository's badge loads for anyone and is cached for a minute. |
| 914 | A private repository's loads only for someone who can see the repository, |
| 915 | so it does not show in a README read anywhere else. |
| 916 | |
| 917 | ### Masking secrets |
| 918 | |
| 919 | Every secret's value is replaced with `***` wherever a job prints it, and |
| 920 | so is every value a step masks with `::add-mask::`. Each is masked in the |
| 921 | forms 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 | |
| 930 | Annotations' titles and messages, and step names, are masked the same way. |
| 931 | A job output that holds a secret in any of those forms is left out, with |
| 932 | a warning in the log, since outputs go to other jobs and to the run's page. |
| 933 | A value of one character is not masked: it would hide that character |
| 934 | everywhere. |
| 935 | |
| 936 | ## Pull requests |
| 937 | |
| 938 | A pull request's workflows run on each new head: when it is opened, when |
| 939 | a commit is pushed to it, and, for one g1t makes, when g1t |
| 940 | marks it ready, which on g1t is when it first has code. Each head runs |
| 941 | each workflow once. |
| 942 | |
| 943 | They also start on the activity types `reopened` (also run by |
| 944 | default, 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 |
| 947 | a 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 |
| 951 | request's `branches` filter, `github.base_ref` and |
| 952 | `pull_request.base.ref` are the branch it merges into, which is not |
| 953 | always the default branch: see |
| 954 | [pull requests into other branches](/guides/base-branches/). |
| 955 | |
| 956 | A pull request from outside the workspace may wait for approval before |
| 957 | its 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 |
| 960 | g1t 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` |
| 962 | on anyone else's. `github.event.issue.requested_by` does the same for an |
| 963 | issue g1t's agent filed. `sender` is whoever caused the event. |
| 964 | |
| 965 | ## Checks |
| 966 | |
| 967 | A 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 |
| 969 | person or an agent, and its runs report a check named after the workflow: |
| 970 | a 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 |
| 972 | is 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 |
| 992 | name: CI |
| 993 | |
| 994 | on: |
| 995 | pull_request: |
| 996 | push: |
| 997 | branches: [main] |
| 998 | merge_group: |
| 999 | ``` |
| 1000 | |
| 1001 | ### Add CI |
| 1002 | |
| 1003 | A repository with no workflows has nothing that proves a change works, for |
| 1004 | people or for agents. Its pull requests, its **Branches and merging** |
| 1005 | settings and its **Actions** page say **This repository has no checks**, |
| 1006 | with an **Add CI** button. Anyone who can push to the repository can use it: |
| 1007 | |
| 1008 | 1. 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. |
| 1015 | 2. 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`. |
| 1018 | 3. Change it on the pull request if the steps are not how your project |
| 1019 | builds, and merge it. |
| 1020 | 4. 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 | |
| 1027 | Secrets are read as `${{ secrets.KEY }}` and config as `${{ vars.KEY }}`, |
| 1028 | from the rows under **Settings → Secrets and variables** that are |
| 1029 | available to Workflows. A job with `environment: production` reads each |
| 1030 | key's Production row; other jobs read the rows for all environments. A |
| 1031 | job 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, |
| 1034 | environments and the workspace's rows work. |
| 1035 | |
| 1036 | Every job also gets `${{ secrets.G1T_TOKEN }}`, [its own token](#the-jobs-token), |
| 1037 | with `GITHUB_TOKEN` as its alias. A pull request's runs get secrets only |
| 1038 | when its author has the Write [role](/guides/access-and-roles/) or higher |
| 1039 | on the repository, a member or an outside collaborator, or is g1t working |
| 1040 | on its own. For a pull request g1t made, its author is g1t and the person |
| 1041 | who asked for it is the one whose role counts. Anyone else's, such as one |
| 1042 | from a fork or by someone with Read or Triage, runs without secrets and |
| 1043 | with 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 | |
| 1048 | Each 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, |
| 1075 | which then ignores the workflow's. Once either is written, every permission |
| 1076 | it leaves out is `none`: |
| 1077 | |
| 1078 | ```yaml |
| 1079 | permissions: |
| 1080 | contents: read |
| 1081 | |
| 1082 | jobs: |
| 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 |
| 1108 | none, so the token cannot even clone a private repository. A reusable |
| 1109 | workflow's jobs get no more than the job that calls it. |
| 1110 | |
| 1111 | There is no `workflows` permission for a job's token: it can never add, |
| 1112 | change or delete a file under `.g1t/workflows/` or `.github/workflows/`, |
| 1113 | even with `contents: write`. A push that does is declined, naming the file, |
| 1114 | so a workflow cannot rewrite the workflows that run with its repository's |
| 1115 | secrets. To change workflows from a job, push with a |
| 1116 | [fine-grained token](/guides/authentication/#workflow-files) that has the |
| 1117 | Workflows permission, kept as a secret. |
| 1118 | |
| 1119 | The job's token is the repository's workspace acting with the Write role |
| 1120 | at most, never Admin: it cannot manage webhooks, secrets, deploy keys or who |
| 1121 | has access, whatever it asks for. |
| 1122 | |
| 1123 | **Without `permissions:`** a job gets the repository's default, which |
| 1124 | someone 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 | |
| 1131 | The workspace's owners set that default, and a **maximum**, under the |
| 1132 | workspace's **Settings → Actions**: with a maximum of **Read only**, no |
| 1133 | repository's default goes past `contents: read` and `packages: read`, |
| 1134 | whatever it chose. Workflows that write `permissions:` get what they |
| 1135 | write either way, and whatever a workflow asks for, a pull request from |
| 1136 | outside the repository's writers (a fork, or someone with Read or Triage) |
| 1137 | gets a token that can only read. |
| 1138 | |
| 1139 | **Allow g1t Actions to create and approve pull requests** is off unless a |
| 1140 | repository's admin turns it on under **Settings → Actions**, and they can |
| 1141 | only where the workspace's owners allow it. Until then a job's token |
| 1142 | cannot open a pull request or approve one, whatever its `pull-requests` |
| 1143 | permission says; it can still read, comment on, review with changes |
| 1144 | requested, and merge them. |
| 1145 | |
| 1146 | The token can never change secrets, variables, environments' rules or |
| 1147 | the Actions settings, approve runs or deployments, or reach another |
| 1148 | repository. |
| 1149 | |
| 1150 | ## Environments |
| 1151 | |
| 1152 | A job that names an environment with `environment:` reads that |
| 1153 | environment's [secrets and variables](/guides/secrets-and-variables/#a-value-per-environment) |
| 1154 | and records a [deployment](/guides/deployments-api/#deployments-from-g1t-actions) |
| 1155 | to it. Give the environment protection rules, and such a job waits until |
| 1156 | they 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 | |
| 1166 | To set them: |
| 1167 | |
| 1168 | 1. Open the repository's **Settings → Environments**. It lists every |
| 1169 | environment your workflows, secrets and deployments name. |
| 1170 | 2. Choose one, or name a new one, and set its rules. |
| 1171 | 3. Save. Runs that reach the environment from then on wait by them. |
| 1172 | |
| 1173 | A run whose jobs wait shows **Waiting for review** at the top of its page, |
| 1174 | with the environments, the jobs each holds, its reviewers and when its |
| 1175 | wait timer runs out. Reviewers are told in their [inbox](/guides/inbox/); |
| 1176 | on the run's page they choose **Approve and deploy** or **Reject**, with |
| 1177 | room for a comment. One review covers every job of the run that names the |
| 1178 | environment. A rejected job fails, and so does anything that needs it. The |
| 1179 | rest of the run goes on meanwhile: jobs that do not need the waiting ones |
| 1180 | run. |
| 1181 | |
| 1182 | The environment's name may be an expression, such as |
| 1183 | `environment: ${{ inputs.target }}`: it is read once the job's needs are |
| 1184 | done, and its rules and secrets are that environment's. Names are matched |
| 1185 | without regard to case. |
| 1186 | |
| 1187 | From the API, `PUT /repos/{owner}/{repo}/environments/{environment}` sets |
| 1188 | the 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` |
| 1191 | removes them. `POST /repos/{owner}/{repo}/actions/runs/{id}/pending_deployments` |
| 1192 | approves or rejects a run's waiting jobs: |
| 1193 | |
| 1194 | ```sh |
| 1195 | curl -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 | |
| 1201 | A job's own token cannot approve, reject or change any of it. |
| 1202 | |
| 1203 | ## Pull requests from outside |
| 1204 | |
| 1205 | A pull request from someone outside the workspace runs code anyone could |
| 1206 | have 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 |
| 1209 | page. Nothing runs before then: no job starts, and no token or secret is |
| 1210 | handed 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 | |
| 1218 | Members' pull requests never wait, nor do pull requests g1t opens on its |
| 1219 | own. For a pull request g1t made for someone, that person is the one whose |
| 1220 | policy counts. Each new push to the pull request waits again. |
| 1221 | `pull_request_target` runs, which run the default branch's workflow and |
| 1222 | code, never wait. |
| 1223 | |
| 1224 | From the API: `POST /repos/{owner}/{repo}/actions/runs/{id}/approve` |
| 1225 | approves a run, and `GET` and `PUT |
| 1226 | /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval` |
| 1227 | read and set the policy, as `approval_policy`. |
| 1228 | |
| 1229 | ## Repository dispatch |
| 1230 | |
| 1231 | `POST /repos/{owner}/{repo}/dispatches` starts the default branch's |
| 1232 | workflows that run `on: repository_dispatch` for its `event_type`, those |
| 1233 | listing it under `types:` or listing none. `client_payload` is theirs to |
| 1234 | read as `github.event.client_payload`: |
| 1235 | |
| 1236 | ```yaml |
| 1237 | on: |
| 1238 | repository_dispatch: |
| 1239 | types: [docs-published] |
| 1240 | |
| 1241 | jobs: |
| 1242 | announce: |
| 1243 | runs-on: ubuntu-latest |
| 1244 | steps: |
| 1245 | - run: echo "Docs ${{ github.event.client_payload.version }} are out" |
| 1246 | ``` |
| 1247 | |
| 1248 | ```sh |
| 1249 | curl -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 | |
| 1254 | It needs the Write role, or a token with `code:write`; a job's own token |
| 1255 | needs `contents: write`. `client_payload` is a JSON object of at most 10 |
| 1256 | properties and 64 KB. |
| 1257 | |
| 1258 | ## Who may run workflows |
| 1259 | |
| 1260 | What 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 | |
| 1271 | Jobs 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. |
| 1275 | On a public repository, |
| 1276 | [g1t's open-source pool](/guides/usage-and-billing/#the-open-source-pool) |
| 1277 | runs them too, after a card check, until the month's pool is spent. |
| 1278 | |
| 1279 | Before each job starts, g1t reserves what it may cost (its time limit at |
| 1280 | the sandbox price) with billing, and settles what it really cost when it |
| 1281 | ends; each job's sandbox is charged as |
| 1282 | [sandbox time](/guides/usage-and-billing/#sandbox-time), from the first |
| 1283 | second. A job billing refuses does not start: it is recorded as failed |
| 1284 | with "Not started:" and the reason, such as "Workflows run in g1t's |
| 1285 | sandboxes, which cost real money, so they need the g1t plan ($20 a month) |
| 1286 | or a card check", and what to do about it. The |
| 1287 | Actions page tells people with Write on a repository whose workspace |
| 1288 | cannot run jobs before the first run. |
| 1289 | |
| 1290 | ## From the API |
| 1291 | |
| 1292 | The routes follow the standard Actions REST shape, so existing scripts |
| 1293 | usually 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 | |
| 1325 | Secrets 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 | |
| 1332 | Workspace secrets and variables are under |
| 1333 | `/workspaces/{workspace}/actions/secrets` and `…/variables`. The fields |
| 1334 | g1t adds (environments, who reads a row, linked repositories) are in |
| 1335 | [Secrets and variables](/guides/secrets-and-variables/#from-the-api). |
| 1336 | |
| 1337 | ```sh |
| 1338 | curl -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 | |
| 1343 | To save an artifact from a script, follow the redirect: |
| 1344 | |
| 1345 | ```sh |
| 1346 | curl -L -o web-dist.zip -H "Authorization: Bearer $G1T_TOKEN" \ |
| 1347 | https://api.g1t.sh/repos/acme/web/actions/artifacts/4182/zip |
| 1348 | ``` |