g1t/apps/docs/src/content/docs/guides/actions.md
| 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` | The same, from g1t's own pushes, pull requests, issues, comments and [merge queue](/guides/merge-queue/). | |
| 33 | | `jobs`, `needs`, `if`, `outputs`, `env`, `defaults`, `timeout-minutes`, `continue-on-error` | The same. | |
| 34 | | `strategy.matrix` with `include` and `exclude`, `fail-fast`, `max-parallel`, a matrix from `fromJSON(needs.…)` | The same. | |
| 35 | | `concurrency` with `cancel-in-progress` | The same. | |
| 36 | | `${{ }}` expressions: every operator, function and context | The same, including `hashFiles`, `success()`, `failure()`, `always()` and `cancelled()`. | |
| 37 | | `run:` with `bash`, `sh`, `python` or a custom shell | The same. | |
| 38 | | JavaScript actions (`uses: owner/repo@v7`) | Fetched from GitHub and run as they are, on Node 24, the runtime current actions declare. | |
| 39 | | Composite actions | The same. | |
| 40 | | Reusable workflows in the repository (`jobs.<id>.uses: ./.g1t/workflows/build.yml`) | The same: `with:` inputs, `on.workflow_call` outputs, and nesting up to four deep. `./.github/workflows/…` finds the workflow under `.g1t/` after the move. Their jobs read the repository's secrets and variables. | |
| 41 | | `actions/checkout` | Checks out from g1t, with `ref`, `fetch-depth`, `path`, `repository`, `token` and `submodules`. | |
| 42 | | `GITHUB_OUTPUT`, `GITHUB_ENV`, `GITHUB_PATH`, `GITHUB_STATE`, `GITHUB_STEP_SUMMARY` | The same. | |
| 43 | | `::error::`, `::warning::`, `::notice::`, `::group::`, `::add-mask::` | The same: errors and warnings become annotations on the run. | |
| 44 | | `secrets.*`, `vars.*`, `secrets.GITHUB_TOKEN` | The same. `secrets.G1T_TOKEN` is the workspace's own token for the run; `GITHUB_TOKEN` is its alias. | |
| 45 | | `environment:` on a job | The job reads each key's row for that environment, as GitHub's environment secrets work. | |
| 46 | | `actions/upload-artifact`, `actions/download-artifact` | Kept with the run for 14 days, passed between its jobs, and downloadable from the run's page. Up to 60 MB each. | |
| 47 | | `actions/cache`, `actions/cache/restore`, `actions/cache/save` | Kept per repository, found by `key` or the newest under a `restore-keys` prefix. `path` takes globs and `!` exclusions. Up to 2 GB each; see [the cache](#the-cache). | |
| 48 | |
| 49 | The **Actions** page of a workflow says, under *How this runs on g1t*, |
| 50 | anything in it that runs differently. |
| 51 | |
| 52 | ### Not yet |
| 53 | |
| 54 | - **Windows and macOS on g1t's machines.** g1t's own runners are Linux; a |
| 55 | job with `runs-on: windows-latest` or `macos-latest` fails, and says so. |
| 56 | [Self-hosted runners](/guides/self-hosted-runners/) of any OS run them: |
| 57 | `runs-on: [self-hosted, windows]`. |
| 58 | - **Docker** container actions, `services:` containers and `container:`. |
| 59 | - **Reusable workflows from other repositories** (`uses: owner/repo/.github/workflows/x.yml@v1`); ones in the same repository work. |
| 60 | - **The toolkit's own cache.** Actions that cache through GitHub's service |
| 61 | themselves, such as `actions/setup-node` with `cache: npm`, run without |
| 62 | it. Use `actions/cache` for the same effect. |
| 63 | - **Environments' protection rules** (required reviewers, wait timers, |
| 64 | branch limits). A job with `environment:` gets that environment's |
| 65 | [values](/guides/secrets-and-variables/#a-value-per-environment), and runs |
| 66 | without waiting. |
| 67 | |
| 68 | ## The runner |
| 69 | |
| 70 | Jobs run in a fresh sandbox each: Debian with Node 24, Python 3, Go, Rust, |
| 71 | `build-essential`, `git`, `curl`, `jq` and passwordless `sudo`, in GitHub's |
| 72 | layout (`/home/runner/work`, `RUNNER_TEMP`, `RUNNER_TOOL_CACHE`). |
| 73 | `runner.os` is `Linux`. `ubuntu-latest`, `ubuntu-24.04`, `self-hosted` and |
| 74 | other Linux labels all run here. Setup actions such as |
| 75 | `actions/setup-node` and `actions/setup-python` install other versions as |
| 76 | they do on GitHub. |
| 77 | |
| 78 | ### Machine sizes |
| 79 | |
| 80 | A job runs on the standard machine unless its `runs-on` names a larger |
| 81 | one: |
| 82 | |
| 83 | | `runs-on` | vCPUs | Memory | Disk | |
| 84 | | --- | --- | --- | --- | |
| 85 | | `ubuntu-latest`, or any other Linux label | 0.5 | 4 GiB | 8 GB | |
| 86 | | `g1t-2core` | 2 | 8 GiB | 16 GB | |
| 87 | | `g1t-4core` | 4 | 12 GiB | 20 GB | |
| 88 | |
| 89 | ```yaml |
| 90 | jobs: |
| 91 | build: |
| 92 | runs-on: g1t-4core |
| 93 | ``` |
| 94 | |
| 95 | The label can come from the matrix or the run's inputs |
| 96 | (`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). A |
| 97 | larger machine costs what it costs g1t, plus the same margin as all |
| 98 | sandbox time: see [usage and billing](/guides/usage-and-billing/#workflow-jobs-on-larger-machines). |
| 99 | Builds that compile, such as Rust or a large TypeScript project, finish |
| 100 | several times faster on one. |
| 101 | |
| 102 | A job runs for at most 60 minutes, whatever its `timeout-minutes`, and |
| 103 | for less if the workspace's plan caps runs lower (a new workspace's first |
| 104 | month, or the trial). A job stopped at its time cap fails saying so. |
| 105 | |
| 106 | ### What a job can reach |
| 107 | |
| 108 | A job's network is restricted, as an agent's is (see |
| 109 | [guardrails](/guides/guardrails/)): it reaches the hosts its project's |
| 110 | guardrails allow, g1t itself, and what builds need, and nothing else. |
| 111 | What builds need is the package registries (npm, PyPI, crates.io, the Go |
| 112 | proxy, RubyGems, Packagist, NuGet, Maven and Gradle, Debian's mirrors), |
| 113 | GitHub, where `uses:` actions and the setup actions' downloads come from, |
| 114 | and the toolchains' download sites (`nodejs.org`, `go.dev`, |
| 115 | `static.rust-lang.org`). A request anywhere else gets `403` with |
| 116 | the reason. To reach another host, someone with the Maintain [role](/guides/access-and-roles/) or |
| 117 | higher adds it to the project's |
| 118 | allowed domains under **Settings → Guardrails**; a project whose guardrails |
| 119 | turn the network restriction off runs its jobs with an open network. |
| 120 | |
| 121 | A host only workflows should reach, such as the API a deploy uploads to, |
| 122 | goes in **Workflow-only domains** instead, limited to the workflows and |
| 123 | environments that need it: `api.cloudflare.com | deploy.yml | production` |
| 124 | lets only `deploy.yml`'s jobs with `environment: production` reach it. |
| 125 | Agents never reach those hosts, and neither do runs of pull requests from |
| 126 | forks. See [workflow-only domains](/guides/guardrails/#workflow-only-domains). |
| 127 | |
| 128 | g1t does not run cryptocurrency miners: a step that names one (`xmrig`, |
| 129 | a `stratum+tcp://` pool, `--donate-level`) is not run, and a job that |
| 130 | looks like it is mining is stopped. See |
| 131 | [abuse and mining](/guides/guardrails/#abuse-and-mining). |
| 132 | |
| 133 | ## The cache |
| 134 | |
| 135 | `actions/cache` keeps what a job saves for the repository's later jobs: |
| 136 | |
| 137 | | | | |
| 138 | | --- | --- | |
| 139 | | One entry | Up to 2 GB, compressed. A larger one is not saved, and the job goes on. | |
| 140 | | A repository's entries | Up to 10 GB together. Saving past it removes the entries restored longest ago. | |
| 141 | | How long | Until it has not been restored for 7 days, and at most 28 days after it was saved. | |
| 142 | | Keys | Written once: saving under a key that exists does nothing. A restore finds its `key` exactly, else the newest entry whose key starts with one of its `restore-keys`. | |
| 143 | | `path` | Files and folders; globs, `**` included; `~/` is the home folder; a line starting with `!` leaves matching paths out. | |
| 144 | | Compression | zstd. | |
| 145 | |
| 146 | ```yaml |
| 147 | - uses: actions/cache@v4 |
| 148 | with: |
| 149 | path: | |
| 150 | ~/.cargo/registry/cache |
| 151 | target/release |
| 152 | !target/**/incremental |
| 153 | key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} |
| 154 | restore-keys: cargo-${{ runner.os }}- |
| 155 | ``` |
| 156 | |
| 157 | Each restore and save says on the job's log how large the entry was and |
| 158 | how long it took. A workspace on the plan pays for what its caches hold |
| 159 | (`Actions cache storage` on its statement), at R2's price plus the margin; |
| 160 | see [usage and billing](/guides/usage-and-billing/#actions-cache). |
| 161 | |
| 162 | ## Runs and logs |
| 163 | |
| 164 | Open a repository's **Actions** page, in its sidebar. Pick a workflow to |
| 165 | see its runs, run it by hand if it has `workflow_dispatch`, or turn it off |
| 166 | without touching its file. |
| 167 | |
| 168 | A run's page shows its jobs, each job's steps, and their logs as they are |
| 169 | written. Groups fold, errors and warnings are marked, and secrets are |
| 170 | replaced with `***`. **Cancel**, **Re-run all jobs** and **Re-run failed |
| 171 | jobs** do what they say. |
| 172 | |
| 173 | ## Pull requests |
| 174 | |
| 175 | A pull request's workflows run on each new head: when it is opened, when |
| 176 | a commit is pushed to it, and, for one a g1t agent makes, when the agent |
| 177 | marks it ready, which on g1t is when it first has code. Each head runs |
| 178 | each workflow once. |
| 179 | |
| 180 | ## Checks |
| 181 | |
| 182 | A pull request's checks are its workflows. Each workflow that runs on |
| 183 | `pull_request` runs on every pull request's head, whoever opened it, a |
| 184 | person or an agent, and its runs report a check named after the workflow: |
| 185 | a workflow with `name: CI` reports `CI`, with the status context |
| 186 | `CI / pull_request` (the workflow's name and the event). |
| 187 | |
| 188 | - **Which checks a merge needs** is up to the default branch's |
| 189 | [required status checks](/guides/pull-requests/#required-status-checks), |
| 190 | under **Settings → Branches and merging**. A required check that failed, |
| 191 | is still running or has not reported holds the merge. Checks that are not |
| 192 | required are shown on the pull request and never hold it. |
| 193 | - **In a repository that merges through the [merge queue](/guides/merge-queue/)**, |
| 194 | workflows with `on: merge_group` run on each combined state the queue |
| 195 | builds, on the branch `g1t-queue/<entry>`, and the state lands only if |
| 196 | they and every required check pass on it. A workflow behind a required |
| 197 | check needs `merge_group` in its `on:`. |
| 198 | - **A pull request a g1t agent is working on** goes back to the agent when |
| 199 | a check fails, with the end of each failed job's log. The agent reads the |
| 200 | run and its logs with the same tools you have, fixes the cause, and |
| 201 | pushes; the workflows run again. See |
| 202 | [seeing it through](/guides/g1t-agents/#seeing-it-through). |
| 203 | |
| 204 | ```yaml |
| 205 | name: CI |
| 206 | |
| 207 | on: |
| 208 | pull_request: |
| 209 | push: |
| 210 | branches: [main] |
| 211 | merge_group: |
| 212 | ``` |
| 213 | |
| 214 | ### Add CI |
| 215 | |
| 216 | A repository with no workflows has nothing that proves a change works, for |
| 217 | people or for agents. Its pull requests, its **Branches and merging** |
| 218 | settings and its **Actions** page say **This repository has no checks**, |
| 219 | with an **Add CI** button. Anyone who can push to the repository can use it: |
| 220 | |
| 221 | 1. Choose **Add CI**. g1t looks at the files at the repository's root and |
| 222 | writes a starter workflow with a job for each stack it finds, up to |
| 223 | three: Node (npm, pnpm, Yarn or Bun), Rust, Go, Python (pip or uv), Ruby, |
| 224 | Java (Maven or Gradle), .NET, or Make. Each job installs, lints where |
| 225 | the project says how, builds and tests. When it finds none, the job is a |
| 226 | placeholder that fails until you replace its last step with your own |
| 227 | commands. |
| 228 | 2. The workflow is committed as `.g1t/workflows/ci.yml` on a new branch, |
| 229 | `add-ci`, and opened as a pull request, by you. It is named `CI` and runs |
| 230 | on `pull_request`, on `push` to the default branch, and on `merge_group`. |
| 231 | 3. Change it on the pull request if the steps are not how your project |
| 232 | builds, and merge it. |
| 233 | 4. Once it has run, `CI` is offered under **Required status checks**. |
| 234 | Require it, so that nothing merges into the default branch unless it |
| 235 | passes. |
| 236 | |
| 237 | ## Secrets and variables |
| 238 | |
| 239 | Secrets are read as `${{ secrets.KEY }}` and config as `${{ vars.KEY }}`, |
| 240 | from the rows under **Settings → Secrets and variables** that are |
| 241 | available to Workflows. A job with `environment: production` reads each |
| 242 | key's Production row; other jobs read the rows for all environments. See |
| 243 | [Secrets and variables](/guides/secrets-and-variables/) for how rows, |
| 244 | environments and the workspace's rows work. |
| 245 | |
| 246 | Every trusted job also gets `${{ secrets.G1T_TOKEN }}`, the workspace's own |
| 247 | token for the run, with `GITHUB_TOKEN` as its alias. A pull request's runs |
| 248 | get secrets and the token only when its author has the Write |
| 249 | [role](/guides/access-and-roles/) or higher on the repository, a member or |
| 250 | an outside collaborator, or is g1t's agent. Anyone else's, such as one |
| 251 | from a fork or by someone with Read or Triage, runs without secrets and |
| 252 | with an empty token. See |
| 253 | [who gets secrets](/guides/secrets-and-variables/#who-gets-secrets). |
| 254 | |
| 255 | ## Who may run workflows |
| 256 | |
| 257 | What you can do with a repository's workflows follows your |
| 258 | [role](/guides/access-and-roles/) on it: |
| 259 | |
| 260 | | | Needs | |
| 261 | | --- | --- | |
| 262 | | See workflows, runs and their logs | Read: on a public repository, anyone | |
| 263 | | Run a workflow by hand, cancel or re-run a run | Write | |
| 264 | | Enable or disable a workflow | Maintain | |
| 265 | | The repository's secrets and variables, seeing them included | Admin | |
| 266 | |
| 267 | Jobs run in g1t's sandboxes, so they need the |
| 268 | [g1t plan](/guides/usage-and-billing/#the-g1t-plan) or |
| 269 | [the trial](/guides/usage-and-billing/#the-trial). On a public repository, |
| 270 | [g1t's open-source pool](/guides/usage-and-billing/#the-open-source-pool) |
| 271 | runs them too, after a card check, until the month's pool is spent. |
| 272 | |
| 273 | Before each job starts, g1t reserves what it may cost (its time limit at |
| 274 | the sandbox price) with billing, and settles what it really cost when it |
| 275 | ends; each job's sandbox is charged as |
| 276 | [sandbox time](/guides/usage-and-billing/#sandbox-time), from the first |
| 277 | second. A job billing refuses does not start: it is recorded as failed |
| 278 | with "Not started:" and the reason, such as "Workflows run in g1t's |
| 279 | sandboxes, which need a paid workspace", and what to do about it. The |
| 280 | Actions page tells people with Write on a repository whose workspace |
| 281 | cannot run jobs before the first run. |
| 282 | |
| 283 | ## From the API |
| 284 | |
| 285 | The routes follow the standard Actions REST shape, so existing scripts |
| 286 | usually work once they point at `https://api.g1t.sh`. |
| 287 | |
| 288 | | `workflow` action | Route | |
| 289 | | --- | --- | |
| 290 | | `list` | `GET /repos/{owner}/{repo}/actions/workflows` | |
| 291 | | `list_runs` | `GET /repos/{owner}/{repo}/actions/runs`, with `workflow`, `branch`, `event`, `pull`, `head_sha` | |
| 292 | | `get_run` | `GET /repos/{owner}/{repo}/actions/runs/{id}` | |
| 293 | | `job_logs` | `GET /repos/{owner}/{repo}/actions/jobs/{job}/logs?after=` | |
| 294 | | `dispatch` | `POST /repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches` with `ref` and `inputs` | |
| 295 | | `cancel` | `POST /repos/{owner}/{repo}/actions/runs/{id}/cancel` | |
| 296 | | `rerun` | `POST …/runs/{id}/rerun`, or `…/rerun-failed-jobs` | |
| 297 | | `update` | `PUT …/workflows/{workflow}/enable` and `…/disable` | |
| 298 | | `list_actions_secrets`, `set_actions_secret`, `delete_actions_secret` | `GET`, `PUT` and `DELETE /repos/{owner}/{repo}/actions/secrets/{name}` | |
| 299 | | `list_actions_variables`, `set_actions_variable`, `delete_actions_variable` | `GET` and `POST /repos/{owner}/{repo}/actions/variables`, `PATCH` and `DELETE …/variables/{name}` | |
| 300 | |
| 301 | Workspace secrets and variables are under |
| 302 | `/workspaces/{workspace}/actions/secrets` and `…/variables`. The fields |
| 303 | g1t adds (environments, who reads a row, linked repositories) are in |
| 304 | [Secrets and variables](/guides/secrets-and-variables/#from-the-api). |
| 305 | |
| 306 | ```sh |
| 307 | curl -X POST https://api.g1t.sh/repos/acme/web/actions/workflows/ci.yml/dispatches \ |
| 308 | -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \ |
| 309 | -d '{"ref": "main", "inputs": {"environment": "staging"}}' |
| 310 | ``` |
| 311 |