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 e03e530a..1c31a8fe 100644
--- a/ci/dynamic-ci/getting-started/github-actions.mdx
+++ b/ci/dynamic-ci/getting-started/github-actions.mdx
@@ -1,105 +1,99 @@
---
title: "GitHub Actions"
-description: "Add the Dynamic CI filter job to a GitHub Actions workflow."
+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
-## Add the filter job to the top of your workflow
+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.
-List every job you want to gate under `outputs`. Check out first, with full
-history, so the action can read the changed files.
+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.
+
+## Workflow Example
+
+This workflow gates two jobs. Note the job ids are the critical matched. values in this flow
```yaml
+name: CI
+
+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.ci-filter.outputs.unit-tests }}
- integration-tests: ${{ steps.ci-filter.outputs.integration-tests }}
+ unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }}
+ playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
with:
- fetch-depth: 0
- filter: blob:none
- - name: Dynamic CI
- id: ci-filter
- uses: trunk-io/dynamic-ci@v1
+ clean: false
+ - 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]
+ if: >-
+ !cancelled() &&
+ needs.dynamic-ci.outputs.unit-tests != 'false'
+ steps:
+ - uses: actions/checkout@v7
+ - run: make 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
+ playwright-tests:
+ name: Playwright Tests
+ runs-on: ubuntu-latest
+ needs: [dynamic-ci]
+ if: >-
+ !cancelled() &&
+ needs.dynamic-ci.outputs.playwright-tests != 'false'
+ steps:
+ - uses: actions/checkout@v7
+ - run: make test
```
-
- Gate on `!= 'false'`, never on `== 'true'`. A job with no verdict has no
- output, and `!= 'false'` makes it run.
-
+## Guidance for gating a job correctly
-Use each job's key from `jobs:`, not its display `name:`.
+
+
+ Outputs are named by the key under `jobs:` In the example above playwright-tests ad unit-tests are the job ids.
-
- 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.
+ 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
- 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. Ask us to turn the signal on and set each job's limit.
-
-
-## 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).
+
+ ```yaml
+ outputs:
+ unit-tests: ${{ steps.dynamic-ci.outputs.unit-tests }}
+ playwright-tests: ${{ steps.dynamic-ci.outputs.playwright-tests
+ ```
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ Join other conditions with &&
+
+
+
+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.
+
+## Check your setup
+
+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/ci/dynamic-ci/getting-started/index.mdx b/ci/dynamic-ci/getting-started/index.mdx
index 1f0ea0a2..09ba4492 100644
--- a/ci/dynamic-ci/getting-started/index.mdx
+++ b/ci/dynamic-ci/getting-started/index.mdx
@@ -5,16 +5,15 @@ 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.
-
- 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).
+
+
+
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"
]
}