From 609e3ccbec181c39e4a85afd1c4fc97df2b678dc Mon Sep 17 00:00:00 2001 From: Eli Schleifer Date: Wed, 4 Nov 2026 00:21:55 +0000 Subject: [PATCH 01/28] Dynamic CI: a complete GitHub Actions setup guide Rewrite the GitHub Actions setup page around the gating patterns trunk2 runs on its own workflows: a complete example, the rules every gated job must follow, required checks, which events to filter, every action input, and how to check the setup in the Issues tab. Co-Authored-By: Claude Opus 5.5 --- .../getting-started/github-actions.mdx | 219 +++++++++++++++--- 1 file changed, 193 insertions(+), 26 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index e03e530a..33c8a18b 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -1,6 +1,6 @@ --- title: "GitHub Actions" -description: "Add the Dynamic CI filter job to a GitHub Actions workflow." +description: "Add Dynamic CI to a GitHub Actions workflow: a complete example workflow, the rules every gated job must follow, how to keep required checks working, and how to check the setup." og:title: "Set up Trunk Dynamic CI for GitHub Actions" --- @@ -11,20 +11,50 @@ og:title: "Set up Trunk Dynamic CI for GitHub Actions" as a repository secret named `TRUNK_API_TOKEN`. -## Add the filter job to the top of your workflow +## How it works -List every job you want to gate under `outputs`. Check out first, with full -history, so the action can read the changed files. +A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` +action. The action asks Trunk which jobs this change needs, and writes one +output per job, named by the job's key under `jobs:`. The value is `'true'` +(run) or `'false'` (skip). Each job you want Dynamic CI to decide reads its +output in its `if:`. + +A job runs unless its output is exactly `'false'`. If Trunk is down, the token +is missing, or a job has no verdict yet, the output is empty and the job runs. +This only holds if every job follows the [rules](#rules-for-every-gated-job) +below. + +## Complete example + +This workflow gates two jobs and a matrix job, and has one job to mark as the +required check. Copy it, then replace the job keys and steps with your own. ```yaml +name: CI + +on: + pull_request: {} + push: + branches: + - main + - trunk-merge/** # Trunk Merge Queue's test branches + +permissions: + contents: read + pull-requests: read + jobs: dynamic-ci-filter: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 + # Every gated job runs after a merge to main. + if: github.ref != 'refs/heads/main' + # One output per gated job, named by its job key. outputs: unit-tests: ${{ steps.ci-filter.outputs.unit-tests }} integration-tests: ${{ steps.ci-filter.outputs.integration-tests }} + e2e: ${{ steps.ci-filter.outputs.e2e }} steps: - uses: actions/checkout@v4 with: @@ -35,32 +65,154 @@ jobs: uses: trunk-io/dynamic-ci@v1 with: token: ${{ secrets.TRUNK_API_TOKEN }} -``` -## Gate your jobs on it + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + needs: [dynamic-ci-filter] + if: >- + !cancelled() && + needs.dynamic-ci-filter.outputs.unit-tests != 'false' + steps: + - uses: actions/checkout@v4 + - run: make test + + integration-tests: + name: Integration Tests + runs-on: ubuntu-latest + needs: [dynamic-ci-filter] + if: >- + !cancelled() && + needs.dynamic-ci-filter.outputs.integration-tests != 'false' + steps: + - uses: actions/checkout@v4 + - run: make integration-test -```yaml -unit-tests: - name: Unit Tests - runs-on: ubuntu-latest - needs: [dynamic-ci-filter] - if: >- - !cancelled() && - needs.dynamic-ci-filter.outputs.unit-tests != 'false' - steps: - - run: make test + e2e: + name: E2E + runs-on: ubuntu-latest + needs: [dynamic-ci-filter] + if: >- + !cancelled() && + needs.dynamic-ci-filter.outputs.e2e != 'false' + strategy: + matrix: + browser: [chromium, firefox] + steps: + - uses: actions/checkout@v4 + - run: make e2e BROWSER=${{ matrix.browser }} + + # Mark this job, and only this job, as the required check. + ci-passed: + name: CI Passed + runs-on: ubuntu-latest + timeout-minutes: 5 + if: ${{ !cancelled() }} + needs: [unit-tests, integration-tests, e2e] + steps: + - name: Fail unless every job passed or was skipped + env: + RESULTS: ${{ join(needs.*.result, ' ') }} + run: | + for result in ${RESULTS}; do + case "${result}" in + success | skipped) ;; + *) echo "::error::a job reported '${result}'"; exit 1 ;; + esac + done ``` - - Gate on `!= 'false'`, never on `== 'true'`. A job with no verdict has no - output, and `!= 'false'` makes it run. - +## Rules for every gated job + + + + Outputs are named by the key under `jobs:` (`unit-tests`), the value + `github.job` reports, never by `name:` (`Unit Tests`). Re-export each gated + job's output from the filter job's `outputs:`, under the same key. The + action sets its outputs at runtime, so GitHub cannot pass them on unless + you list them there. + + + + Each gated job has `needs: [dynamic-ci-filter]`. Without it, + `needs.dynamic-ci-filter.outputs` is empty and the job always runs. + + + + By default GitHub skips a job when a job it needs failed or was skipped. + Without `!cancelled()`, a failed filter job (a bad token, an outage) skips + every gated job, and nothing is tested. With it, the output is empty and the + jobs run. + + + + Gate on `!= 'false'`, never on `== 'true'`, so a job with no verdict runs. + Do not read the output on its own (`if: needs.dynamic-ci-filter.outputs.unit-tests`): + `'false'` is a non-empty string, so the job always runs. + + + + Add your own conditions with `&&`. A condition joined with `||` can still be + true when the verdict is `'false'`, so the job never skips: + + ```yaml + if: >- + !cancelled() && + needs.dynamic-ci-filter.outputs.unit-tests != 'false' && + github.actor != 'dependabot[bot]' + ``` + + -Use each job's key from `jobs:`, not its display `name:`. +Trunk checks your workflow files for these mistakes. See +[Check your setup](#check-your-setup). + +## Keep required checks working + +A skipped job counts as passing for a required check, but two cases still +block a pull request: + +- **A skipped matrix job** reports one check under the job's name. Required + checks named for each matrix entry, such as `E2E (chromium)`, wait forever. +- **A workflow skipped by `on.paths` or `on.paths-ignore`** reports no checks + at all, so its required checks wait forever. Keep path rules out of `on:`. + Put them in the job's `if:`, or in [Dynamic CI configuration](/ci/dynamic-ci/configuration). + +Mark one job that sums up the others as required, like `ci-passed` in the +example. It runs unless the run was cancelled, `needs` every gated job, and +passes when each job passed or was skipped. Do not add the filter job to its +`needs`: if Trunk is down, the gated jobs run and their results decide the +check. + +## Which events to filter + +- **Pull requests** are scored from your CI history. +- **Merge queues** (Trunk Merge Queue's `trunk-merge/**` branches, or a + `merge_group` event) skip a job only when a path filter in your + [Dynamic CI configuration](/ci/dynamic-ci/configuration) excludes it. See + [Merge queues](/ci/dynamic-ci/merge-queue). +- **Pushes to your default branch** are scored like a pull request. To run + everything after a merge, skip the filter job there, as the example does with + `if: github.ref != 'refs/heads/main'`. Its outputs are then empty, so every + gated job runs. +- **Pull requests from forks** have no access to `TRUNK_API_TOKEN`, so every job + runs. + +## Action inputs + +| Input | Default | Description | +| ------------------- | --------------------- | ----------- | +| `token` | required | Your organization API token. | +| `job-keys` | every job | A job key, or a comma-separated list, to ask about. Leave unset in a filter job, so Trunk decides every job in the workflow. | +| `github-token` | `${{ github.token }}` | Reads the pull request's changed files when the checkout does not have both commits. Needs `pull-requests: read`. | +| `enable-annotation` | `false` | Posts the recommendation to the run's annotations and job summary. | +| `impacts` | unset | Your build tool's measure of how much a change affects. See below. | +| `ignore-signals` | unset | Signals to leave out. An unknown name makes every job run. | A job can ask about itself by passing its own key in `job-keys`, and skip its - own steps. It saves less, because the runner has already started. + own steps. It saves less, because the runner has already started. The same + rules apply: compare with `!= 'false'` and join with `&&`. ```yaml unit-tests: @@ -95,11 +247,26 @@ Use each job's key from `jobs:`, not its display `name:`. Use `"all": true` in place of `count` when everything is affected, and leave `impacts` unset when your tool computed nothing. A bad entry is dropped with - a warning. Ask us to turn the signal on and set each job's limit. + a warning. The signal is off until you ask us to turn it on and set each + job's limit; until then, sending `impacts` changes no verdict. +## Check your setup + +1. Open a pull request. The **Dynamic CI** job's log lists a verdict for each + job. Set `enable-annotation: true` to see them on the run's summary page too. +2. Open [Dynamic CI](https://app.trunk.io/dynamic-ci) in the Trunk app and + select **Issues**. Trunk reads your workflow files on the default branch and + lists each job whose gate cannot skip it: a missing `needs`, an output the + filter job does not export, an output that carries another job's verdict, a + job left out of `job-keys`, or a condition that holds when the verdict is + `'false'`. +3. Your repository starts in **Learning** mode, which records verdicts and skips + nothing. Once the Issues list is empty and the recommendations look right, + [turn on enforcement](/ci/dynamic-ci/settings). + ## Next -[Turn on enforcement](/ci/dynamic-ci/getting-started) once the -recommendations look right. For how Dynamic CI behaves in a merge queue, see -[Merge queues](/ci/dynamic-ci/merge-queue). +For how Dynamic CI behaves in a merge queue, see +[Merge queues](/ci/dynamic-ci/merge-queue). To make a job run on one pull +request whatever Trunk recommends, see [Force Run](/ci/dynamic-ci/force-run). From 31850d0863f75b128e82fa845365ac8c803772c6 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:04:38 -0800 Subject: [PATCH 02/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index 1f0ea0a2..aebe80e5 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -10,11 +10,8 @@ og:title: "Get started with Trunk Dynamic CI" - - Dynamic CI decides from your CI history, so Trunk has to be collecting it. - Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your - organization API token from the **Organization API token (v1)** section of - [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + + decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). From fd08873ff293ffce9428c8e11a0a458c65557adc Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:06:04 -0800 Subject: [PATCH 03/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index aebe80e5..cf233671 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -10,8 +10,11 @@ og:title: "Get started with Trunk Dynamic CI" - - decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + + decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + + + From 84bc851336e7b118f5c47b5078f73e41dbd79e3c Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:06:35 -0800 Subject: [PATCH 04/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index cf233671..de288153 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -11,10 +11,10 @@ og:title: "Get started with Trunk Dynamic CI" - decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). - + From 13a9902a8b19f4b2a2d43c04fea49b79d10f1894 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:07:29 -0800 Subject: [PATCH 05/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index de288153..a91792be 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -11,10 +11,10 @@ og:title: "Get started with Trunk Dynamic CI" - decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). + decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). - + From 3dd59b078fbbcd75ba2354cdeed0a1654c6187a4 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:08:47 -0800 Subject: [PATCH 06/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index a91792be..68a8e829 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -5,8 +5,7 @@ og:title: "Get started with Trunk Dynamic CI" --- - Dynamic CI is in **private beta**, and your organization has to be enabled for - it first. [Reach out to Trunk](https://slack.trunk.io) for access. + Dynamic CI is in **private beta**. [Reach out to Trunk](https://slack.trunk.io) for access. @@ -14,7 +13,7 @@ og:title: "Get started with Trunk Dynamic CI" decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). - + From 68ab51ed2aa25b21e0dc993a85d2c5b6c07cb832 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:09:32 -0800 Subject: [PATCH 07/28] Update Getting started - Updated ci/dynamic-ci/getting-started/index.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index 68a8e829..9f6a72fa 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -13,7 +13,7 @@ og:title: "Get started with Trunk Dynamic CI" decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). - + From 00186d9dc69566a00874d07bd3fd6c1066934f64 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:10:11 -0800 Subject: [PATCH 08/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 33c8a18b..00b149bb 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -1,6 +1,6 @@ --- title: "GitHub Actions" -description: "Add Dynamic CI to a GitHub Actions workflow: a complete example workflow, the rules every gated job must follow, how to keep required checks working, and how to check the setup." +description: "Adding Dynamic CI to your existing GitHub Actions workflows" og:title: "Set up Trunk Dynamic CI for GitHub Actions" --- From 8a7543d4c6033e6a60ea912b4ea5d2c65ee88296 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:10:51 -0800 Subject: [PATCH 09/28] Update Getting started and GitHub Actions - Updated ci/dynamic-ci/getting-started/index.mdx - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 7 ------- ci/dynamic-ci/getting-started/index.mdx | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 00b149bb..4cc68340 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -4,13 +4,6 @@ description: "Adding Dynamic CI to your existing GitHub Actions workflows" og:title: "Set up Trunk Dynamic CI for GitHub Actions" --- - - Before you start, [set up Trunk CI for GitHub Actions](/ci/get-started/github-actions) - and copy your organization API token from the **Organization API token (v1)** - section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). Store it - as a repository secret named `TRUNK_API_TOKEN`. - - ## How it works A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` diff --git a/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx index 9f6a72fa..09ba4492 100644 --- a/ci/dynamic-ci/getting-started/index.mdx +++ b/ci/dynamic-ci/getting-started/index.mdx @@ -13,7 +13,7 @@ og:title: "Get started with Trunk Dynamic CI" decides from your CI history, Follow [Set up Trunk CI](/ci/get-started) for your provider, then copy your organization API token from the **Organization API token (v1)** section of [Settings → Developer → API Keys](https://app.trunk.io/settings/developer/api-tokens). - + From bb0426b1210d247cc573c99b9d1971651a96bc45 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:12:28 -0800 Subject: [PATCH 10/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 4cc68340..7d678608 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -6,11 +6,7 @@ og:title: "Set up Trunk Dynamic CI for GitHub Actions" ## How it works -A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` -action. The action asks Trunk which jobs this change needs, and writes one -output per job, named by the job's key under `jobs:`. The value is `'true'` -(run) or `'false'` (skip). Each job you want Dynamic CI to decide reads its -output in its `if:`. +A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set , named by the job's key under `jobs:`. The value is `'true'` (run) or `'false'` (skip). Each job you want Dynamic CI to decide reads its output in its `if:`. A job runs unless its output is exactly `'false'`. If Trunk is down, the token is missing, or a job has no verdict yet, the output is empty and the job runs. From 5fa1b0c18f071be5712ffe9109db7a76eaf27df3 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:14:34 -0800 Subject: [PATCH 11/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 7d678608..a2b864e0 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -6,7 +6,7 @@ og:title: "Set up Trunk Dynamic CI for GitHub Actions" ## How it works -A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set , named by the job's key under `jobs:`. The value is `'true'` (run) or `'false'` (skip). Each job you want Dynamic CI to decide reads its output in its `if:`. +A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set the output variable named after the job to `'false'`

At each job level you will need to add an `if: `clause to read that matching variable. A job runs unless its output is exactly `'false'`. If Trunk is down, the token is missing, or a job has no verdict yet, the output is empty and the job runs. From 27a21ce3b5e0c5246232444031ebe8a7bc9bc475 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:16:47 -0800 Subject: [PATCH 12/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- .../getting-started/github-actions.mdx | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index a2b864e0..368c9602 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -6,34 +6,22 @@ og:title: "Set up Trunk Dynamic CI for GitHub Actions" ## How it works -A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set the output variable named after the job to `'false'`

At each job level you will need to add an `if: `clause to read that matching variable. +A filter job at the top of your workflow runs the `trunk-io/dynamic-ci` action. The action asks Trunk which jobs this change needs, and writes one output per job. The `outputs` of the Dynamic CI step will store the verdict for each job specified. By listing a job in the list you are asking Dynamic CI to weigh in and attempt to manage that job.

If Dynamic CI determines a job should be skipped it will set the output variable named after the job to `'false'.`At each job level you will need to add an `if: `clause to read that matching variable. -A job runs unless its output is exactly `'false'`. If Trunk is down, the token -is missing, or a job has no verdict yet, the output is empty and the job runs. -This only holds if every job follows the [rules](#rules-for-every-gated-job) -below. +If Trunk is offline, the token is missing, or a job has no verdict yet, the output will be empty and the job will run. This allows Dynamic CI to "fail open" so you will not inadvertently skip jobs. -## Complete example +## Workflow Example -This workflow gates two jobs and a matrix job, and has one job to mark as the -required check. Copy it, then replace the job keys and steps with your own. +This workflow gates one single job and a matrix job, and has one job to mark as the required check. ```yaml name: CI on: pull_request: {} - push: - branches: - - main - - trunk-merge/** # Trunk Merge Queue's test branches - -permissions: - contents: read - pull-requests: read jobs: - dynamic-ci-filter: + dynamic-ci: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 From 3a296a014ff8e2284e94a9e0843f1bc54f8c3031 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:19:10 -0800 Subject: [PATCH 13/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 368c9602..4550479a 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -25,18 +25,14 @@ jobs: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 - # Every gated job runs after a merge to main. - if: github.ref != 'refs/heads/main' + if: github.event_name == 'pull_request' # One output per gated job, named by its job key. outputs: - unit-tests: ${{ steps.ci-filter.outputs.unit-tests }} - integration-tests: ${{ steps.ci-filter.outputs.integration-tests }} - e2e: ${{ steps.ci-filter.outputs.e2e }} + unit-tests: ${{ steps.dci.outputs.unit-tests }} + integration-tests: ${{ steps.dci.outputs.integration-tests }} + e2e: ${{ steps.dci.outputs.e2e }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - filter: blob:none + - uses: actions/checkout@v7 - name: Dynamic CI id: ci-filter uses: trunk-io/dynamic-ci@v1 From 190f849b90612334273be234822d3ff8feb590b6 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:21:13 -0800 Subject: [PATCH 14/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 4550479a..928b4e91 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -21,7 +21,7 @@ on: pull_request: {} jobs: - dynamic-ci: + dynamic-ci-filter: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 @@ -33,8 +33,10 @@ jobs: e2e: ${{ steps.dci.outputs.e2e }} steps: - uses: actions/checkout@v7 + with: + clean: false - name: Dynamic CI - id: ci-filter + id: dci uses: trunk-io/dynamic-ci@v1 with: token: ${{ secrets.TRUNK_API_TOKEN }} From b4e7382a7a33757988eb1166c7ad958072266de8 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:21:44 -0800 Subject: [PATCH 15/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 928b4e91..935cc60a 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -35,8 +35,7 @@ jobs: - uses: actions/checkout@v7 with: clean: false - - name: Dynamic CI - id: dci + - id: dci uses: trunk-io/dynamic-ci@v1 with: token: ${{ secrets.TRUNK_API_TOKEN }} @@ -44,7 +43,7 @@ jobs: unit-tests: name: Unit Tests runs-on: ubuntu-latest - needs: [dynamic-ci-filter] + needs: [dynamic-ci] if: >- !cancelled() && needs.dynamic-ci-filter.outputs.unit-tests != 'false' From 9584577b077f0c92d1180edd717bbcc61671d8e4 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:22:55 -0800 Subject: [PATCH 16/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 935cc60a..f6724753 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -21,15 +21,15 @@ on: pull_request: {} jobs: - dynamic-ci-filter: + dynamic-ci: name: Dynamic CI runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name == 'pull_request' # One output per gated job, named by its job key. outputs: - unit-tests: ${{ steps.dci.outputs.unit-tests }} - integration-tests: ${{ steps.dci.outputs.integration-tests }} + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + integration-tests: ${{ steps.dynamic-ci.outputs.integration-tests }} e2e: ${{ steps.dci.outputs.e2e }} steps: - uses: actions/checkout@v7 From 76ec4fe6302c19decc84a82f622c4c6a576f5236 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:25:01 -0800 Subject: [PATCH 17/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- .../getting-started/github-actions.mdx | 54 ++++--------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index f6724753..bd8ea4b8 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -35,8 +35,7 @@ jobs: - uses: actions/checkout@v7 with: clean: false - - id: dci - uses: trunk-io/dynamic-ci@v1 + - uses: trunk-io/dynamic-ci@v1 with: token: ${{ secrets.TRUNK_API_TOKEN }} @@ -46,56 +45,25 @@ jobs: needs: [dynamic-ci] if: >- !cancelled() && - needs.dynamic-ci-filter.outputs.unit-tests != 'false' + needs.dynamic-ci.outputs.unit-tests != 'false' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - run: make test - integration-tests: - name: Integration Tests - runs-on: ubuntu-latest - needs: [dynamic-ci-filter] - if: >- - !cancelled() && - needs.dynamic-ci-filter.outputs.integration-tests != 'false' - steps: - - uses: actions/checkout@v4 - - run: make integration-test - - e2e: - name: E2E + playwright-tests: + name: Playwright Tests runs-on: ubuntu-latest - needs: [dynamic-ci-filter] + needs: [dynamic-ci] if: >- !cancelled() && - needs.dynamic-ci-filter.outputs.e2e != 'false' - strategy: - matrix: - browser: [chromium, firefox] + needs.dynamic-ci.outputs.playwright-tests != 'false' steps: - - uses: actions/checkout@v4 - - run: make e2e BROWSER=${{ matrix.browser }} - - # Mark this job, and only this job, as the required check. - ci-passed: - name: CI Passed - runs-on: ubuntu-latest - timeout-minutes: 5 - if: ${{ !cancelled() }} - needs: [unit-tests, integration-tests, e2e] - steps: - - name: Fail unless every job passed or was skipped - env: - RESULTS: ${{ join(needs.*.result, ' ') }} - run: | - for result in ${RESULTS}; do - case "${result}" in - success | skipped) ;; - *) echo "::error::a job reported '${result}'"; exit 1 ;; - esac - done + - uses: actions/checkout@v7 + - run: make test ``` +
+ ## Rules for every gated job From 80b5ccd388929408c8ef8ce1e063787afd73cdba Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:25:31 -0800 Subject: [PATCH 18/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index bd8ea4b8..e4077f27 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -29,8 +29,7 @@ jobs: # One output per gated job, named by its job key. outputs: unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} - integration-tests: ${{ steps.dynamic-ci.outputs.integration-tests }} - e2e: ${{ steps.dci.outputs.e2e }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} steps: - uses: actions/checkout@v7 with: From 6230f51803d9ecdc0c5aeea57420b68525522e6e Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:26:23 -0800 Subject: [PATCH 19/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index e4077f27..a057203c 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -12,7 +12,7 @@ If Trunk is offline, the token is missing, or a job has no verdict yet, the outp ## Workflow Example -This workflow gates one single job and a matrix job, and has one job to mark as the required check. +This workflow gates two jobs. Note the job ids are the critical matched. values in this flow ```yaml name: CI @@ -61,9 +61,7 @@ jobs: - run: make test ``` -
- -## Rules for every gated job +## Guidance for gating a job correctly From f695e4b18a66ebd0d73faffaec204dcb36179c29 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:27:49 -0800 Subject: [PATCH 20/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index a057203c..82c5f0c7 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -65,11 +65,12 @@ jobs: - Outputs are named by the key under `jobs:` (`unit-tests`), the value - `github.job` reports, never by `name:` (`Unit Tests`). Re-export each gated - job's output from the filter job's `outputs:`, under the same key. The - action sets its outputs at runtime, so GitHub cannot pass them on unless - you list them there. + Outputs are named by the key under `jobs:` In the example above playwright-tests ad unit-tests are the job ids. + + Re-export each gated job's output from the filter job's `outputs:`, under the same key. The action sets its outputs at runtime, so GitHub cannot pass them on unless you list them there. + + + From b173a9cdaffdf9f324219e49d824b63c307cfe64 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:28:49 -0800 Subject: [PATCH 21/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 82c5f0c7..1ea6baf0 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -71,6 +71,13 @@ jobs: + ```yaml + ... + outputs: + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} + ... + ``` From 5a31ed464fd19726929265a38717c47c5ea74ab3 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:29:22 -0800 Subject: [PATCH 22/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 1ea6baf0..4ef18116 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -72,12 +72,26 @@ jobs: ```yaml + + ... outputs: unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} ... ``` + + ```yaml + outputs: + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} + ``` + + ```yaml + outputs: + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} + ``` From 86c7ecbf163b9c878240e422bb464ea4ed8c1b42 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:31:40 -0800 Subject: [PATCH 23/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 4ef18116..6c22f869 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -72,9 +72,9 @@ jobs: ```yaml - - - ... + # [!code --] + # [!code --] + ... # [!code --] outputs: unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} From 49703b06e385bbb2286fb1114e36424dcbe54952 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:35:11 -0800 Subject: [PATCH 24/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- .../getting-started/github-actions.mdx | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 6c22f869..4a8a7c5d 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -70,33 +70,16 @@ jobs: Re-export each gated job's output from the filter job's `outputs:`, under the same key. The action sets its outputs at runtime, so GitHub cannot pass them on unless you list them there. - - ```yaml - # [!code --] - # [!code --] - ... # [!code --] - outputs: - unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} - playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} - ... - ``` - - ```yaml - outputs: - unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} - playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} - ``` - + ```yaml outputs: - unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} - playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }} + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests ``` - - Each gated job has `needs: [dynamic-ci-filter]`. Without it, - `needs.dynamic-ci-filter.outputs` is empty and the job always runs. + + Each managed job has should specify `needs: [dynamic-ci-filter]`. This ensures the dynamic ci verdict will be available to assess before the job ever spins up. From 2ff3411a97f3a5065c15f1c30122614fc6f9af3d Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:36:17 -0800 Subject: [PATCH 25/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- .../getting-started/github-actions.mdx | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 4a8a7c5d..33aa01a5 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -83,28 +83,11 @@ jobs: - By default GitHub skips a job when a job it needs failed or was skipped. - Without `!cancelled()`, a failed filter job (a bad token, an outage) skips - every gated job, and nothing is tested. With it, the output is empty and the - jobs run. + By default GitHub skips a job when a job it needs failed or was skipped. Without `!cancelled()`, a failed filter job (a bad token, an outage) skips every gated job, and nothing is tested. - - Gate on `!= 'false'`, never on `== 'true'`, so a job with no verdict runs. - Do not read the output on its own (`if: needs.dynamic-ci-filter.outputs.unit-tests`): - `'false'` is a non-empty string, so the job always runs. - - - - Add your own conditions with `&&`. A condition joined with `||` can still be - true when the verdict is `'false'`, so the job never skips: - - ```yaml - if: >- - !cancelled() && - needs.dynamic-ci-filter.outputs.unit-tests != 'false' && - github.actor != 'dependabot[bot]' - ``` + + Join other conditions with && From e6e31f42a9f792cd519ce005a9c29a6bb94804ab Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:36:56 -0800 Subject: [PATCH 26/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- ci/dynamic-ci/getting-started/github-actions.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 33aa01a5..544cf0e0 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -91,8 +91,7 @@ jobs: -Trunk checks your workflow files for these mistakes. See -[Check your setup](#check-your-setup). +When your workflow files are updated and start calling into Dynamic CI a background linter will run to verify the format of your yaml file and call out problems. ## Keep required checks working From 1972810f197ec6920917f3c21d65d45ed5211811 Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 09:38:01 -0800 Subject: [PATCH 27/28] Update GitHub Actions - Updated ci/dynamic-ci/getting-started/github-actions.mdx Mintlify-Source: dashboard-editor --- .../getting-started/github-actions.mdx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 544cf0e0..5a28c0d9 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -93,23 +93,6 @@ jobs: When your workflow files are updated and start calling into Dynamic CI a background linter will run to verify the format of your yaml file and call out problems. -## Keep required checks working - -A skipped job counts as passing for a required check, but two cases still -block a pull request: - -- **A skipped matrix job** reports one check under the job's name. Required - checks named for each matrix entry, such as `E2E (chromium)`, wait forever. -- **A workflow skipped by `on.paths` or `on.paths-ignore`** reports no checks - at all, so its required checks wait forever. Keep path rules out of `on:`. - Put them in the job's `if:`, or in [Dynamic CI configuration](/ci/dynamic-ci/configuration). - -Mark one job that sums up the others as required, like `ci-passed` in the -example. It runs unless the run was cancelled, `needs` every gated job, and -passes when each job passed or was skipped. Do not add the filter job to its -`needs`: if Trunk is down, the gated jobs run and their results decide the -check. - ## Which events to filter - **Pull requests** are scored from your CI history. From 1ff60d8601fd660491a8dcea82b6686a9ea7e71b Mon Sep 17 00:00:00 2001 From: Eli Schleifer <1265982+EliSchleifer@users.noreply.github.com> Date: Wed, 4 Nov 2026 10:05:24 -0800 Subject: [PATCH 28/28] Update GitHub Actions and 2 more pages - Updated ci/dynamic-ci/getting-started/github-actions.mdx - Updated ci/dynamic-ci/configuration.mdx - Created ci/dynamic-ci/dynamic-ci-action-reference.mdx - Updated docs.json Mintlify-Source: dashboard-editor --- ci/dynamic-ci/configuration.mdx | 4 +- ci/dynamic-ci/dynamic-ci-action-reference.mdx | 66 ++++++++++++++ .../getting-started/github-actions.mdx | 86 +------------------ docs.json | 1 + 4 files changed, 71 insertions(+), 86 deletions(-) create mode 100644 ci/dynamic-ci/dynamic-ci-action-reference.mdx diff --git a/ci/dynamic-ci/configuration.mdx b/ci/dynamic-ci/configuration.mdx index 22190ec3..1dd4be8b 100644 --- a/ci/dynamic-ci/configuration.mdx +++ b/ci/dynamic-ci/configuration.mdx @@ -1,10 +1,10 @@ --- title: "Configure how Dynamic CI schedules each CI job" sidebarTitle: "Configuration" -description: "Tune Dynamic CI per job in the Trunk web app: set schedules for pull requests and the merge queue, and add path filters that feed the scoring engine." +description: "Tune Dynamic CI per job in the Trunk app" --- -Dynamic CI is configured job by job on the [Configuration tab](https://app.trunk.io/ci/dynamic-ci?tab=configuration) in the Trunk web app. Each job has a schedule that controls how it is evaluated on pull requests and in the merge queue, plus optional path filters that tell Dynamic CI which changes are relevant to it. +Dynamic CI is configured job by job on the [Configuration tab](https://app.trunk.io/ci/dynamic-ci/configuration) in the Trunk web app. Each job has a schedule that controls how it is evaluated on pull requests and in the merge queue, plus optional path filters that tell Dynamic CI which changes are relevant to it. ## Find a job diff --git a/ci/dynamic-ci/dynamic-ci-action-reference.mdx b/ci/dynamic-ci/dynamic-ci-action-reference.mdx new file mode 100644 index 00000000..eeb656cb --- /dev/null +++ b/ci/dynamic-ci/dynamic-ci-action-reference.mdx @@ -0,0 +1,66 @@ +--- +title: "Dynamic CI Action Reference" +sidebarTitle: "Action reference" +description: "Inputs, outputs, modes, and fail-open behavior of the trunk-io/dynamic-ci GitHub Action." +og:title: "trunk-io/dynamic-ci GitHub Action reference" +--- + +The [`trunk-io/dynamic-ci`](https://github.com/trunk-io/dynamic-ci) action connects a GitHub Actions workflow to Dynamic CI. It asks Trunk which jobs the current change needs and writes one output per job. Your jobs read those outputs in their `if:` conditions to decide whether to run. + +## Usage + +The action sets one output per job in the workflow, named by the job key. Outputs are set at runtime, so re-export each one from the filter job's `outputs:` under the same key: + +```yaml +jobs: + dynamic-ci: + runs-on: ubuntu-latest + outputs: + # 'false': Trunk recommends skipping the job. + # Any other value: Trunk recommends running the job. + # Empty: no verdict (Trunk unreachable, missing token, or no history + # yet). The job should run. + unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }} + steps: + - uses: actions/checkout@v7 + with: + clean: false + - uses: trunk-io/dynamic-ci@v1 + with: + # Your organization API token. Store it as a repository or organization + # secret, such as TRUNK_API_TOKEN + # Required. + token: ${{ secrets.TRUNK_API_TOKEN }} + + # Token used to read the pull request's changed files when the checkout + # does not contain both the base and head commits + # Default: ${{ github.token }} + github-token: '' + + # Post the recommendation for each job to the run's annotations and job + # summary, so verdicts show on the run's summary page + # Default: false + enable-annotation: '' +``` + +## Fail-open behavior + +The action is built so a problem never causes a job to be skipped by mistake. It retries requests to Trunk, and when it still cannot get a verdict, the output is empty and the job will run. + +## Checkout + +The action compares the pull request's base and head commits to find the changed files.`actions/checkout` with `clean: false` is enough. When the checkout does not contain both commits, the action falls back to `github-token` to read the changed files. + +## Versioning + +Pin to the major version tag, `trunk-io/dynamic-ci@v1`, to receive compatible updates automatically. + +## GitHub token permissions + +Grant the workflow `pull-requests: read` so `github-token` can read the pull request's changed files: + +```yaml +permissions: + contents: read + pull-requests: read +``` diff --git a/ci/dynamic-ci/getting-started/github-actions.mdx b/ci/dynamic-ci/getting-started/github-actions.mdx index 5a28c0d9..1c31a8fe 100644 --- a/ci/dynamic-ci/getting-started/github-actions.mdx +++ b/ci/dynamic-ci/getting-started/github-actions.mdx @@ -93,89 +93,7 @@ jobs: When your workflow files are updated and start calling into Dynamic CI a background linter will run to verify the format of your yaml file and call out problems. -## Which events to filter - -- **Pull requests** are scored from your CI history. -- **Merge queues** (Trunk Merge Queue's `trunk-merge/**` branches, or a - `merge_group` event) skip a job only when a path filter in your - [Dynamic CI configuration](/ci/dynamic-ci/configuration) excludes it. See - [Merge queues](/ci/dynamic-ci/merge-queue). -- **Pushes to your default branch** are scored like a pull request. To run - everything after a merge, skip the filter job there, as the example does with - `if: github.ref != 'refs/heads/main'`. Its outputs are then empty, so every - gated job runs. -- **Pull requests from forks** have no access to `TRUNK_API_TOKEN`, so every job - runs. - -## Action inputs - -| Input | Default | Description | -| ------------------- | --------------------- | ----------- | -| `token` | required | Your organization API token. | -| `job-keys` | every job | A job key, or a comma-separated list, to ask about. Leave unset in a filter job, so Trunk decides every job in the workflow. | -| `github-token` | `${{ github.token }}` | Reads the pull request's changed files when the checkout does not have both commits. Needs `pull-requests: read`. | -| `enable-annotation` | `false` | Posts the recommendation to the run's annotations and job summary. | -| `impacts` | unset | Your build tool's measure of how much a change affects. See below. | -| `ignore-signals` | unset | Signals to leave out. An unknown name makes every job run. | - - - A job can ask about itself by passing its own key in `job-keys`, and skip its - own steps. It saves less, because the runner has already started. The same - rules apply: compare with `!= 'false'` and join with `&&`. - - ```yaml - unit-tests: - name: Unit Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - filter: blob:none - - name: Dynamic CI - id: ci-filter - uses: trunk-io/dynamic-ci@v1 - with: - token: ${{ secrets.TRUNK_API_TOKEN }} - job-keys: unit-tests - - if: steps.ci-filter.outputs.unit-tests != 'false' - run: make test - ``` - - - - If your build tool measures how much of the codebase a change affects, such - as a count of affected packages, pass it as a JSON list so Trunk runs broad - changes: - - ```yaml - with: - token: ${{ secrets.TRUNK_API_TOKEN }} - impacts: '[{"source":"tainted-packages","count":${{ steps.affected.outputs.count }}}]' - ``` - - Use `"all": true` in place of `count` when everything is affected, and leave - `impacts` unset when your tool computed nothing. A bad entry is dropped with - a warning. The signal is off until you ask us to turn it on and set each - job's limit; until then, sending `impacts` changes no verdict. - - ## Check your setup -1. Open a pull request. The **Dynamic CI** job's log lists a verdict for each - job. Set `enable-annotation: true` to see them on the run's summary page too. -2. Open [Dynamic CI](https://app.trunk.io/dynamic-ci) in the Trunk app and - select **Issues**. Trunk reads your workflow files on the default branch and - lists each job whose gate cannot skip it: a missing `needs`, an output the - filter job does not export, an output that carries another job's verdict, a - job left out of `job-keys`, or a condition that holds when the verdict is - `'false'`. -3. Your repository starts in **Learning** mode, which records verdicts and skips - nothing. Once the Issues list is empty and the recommendations look right, - [turn on enforcement](/ci/dynamic-ci/settings). - -## Next - -For how Dynamic CI behaves in a merge queue, see -[Merge queues](/ci/dynamic-ci/merge-queue). To make a job run on one pull -request whatever Trunk recommends, see [Force Run](/ci/dynamic-ci/force-run). +1. Open a pull request. The **Dynamic CI's** verdict will be reported on each pull request under the dynamic ci tab. Additionally you can see all decisions made by the system in the [Dynamic CI page](https://app.trunk.io/dynamic-ci). +2. Your repository starts in **Learning** mode, which records verdicts but will not actively skip anything. Once the recommendations look right you can [turn on enforcement](/ci/dynamic-ci/settings). diff --git a/docs.json b/docs.json index b87fcc94..7daef4b2 100644 --- a/docs.json +++ b/docs.json @@ -418,6 +418,7 @@ "ci/dynamic-ci/settings", "ci/dynamic-ci/force-run", "ci/dynamic-ci/merge-queue", + "ci/dynamic-ci/dynamic-ci-action-reference", "ci/dynamic-ci/faq" ] }