Skip to main content
Dynamic CI is in private beta. Recommendation quality is still improving, the signals behind a verdict still change between releases, and the action’s inputs and outputs are not yet stable. Pin a specific tag if you need stability, and read the release notes before upgrading.

What it is

Most CI runs are wasted. A docs-only change does not need the integration suite; a frontend typo does not need the backend tests. The usual fix is a path filter — hand-written globs that are coarse, go stale, and encode a guess about which files matter to which job. Dynamic CI answers the same question from evidence instead. Trunk already records every (file change set → job outcome) pair in your repository, so it can tell you which jobs this diff has historically broken and which it never touches. The trunk-io/dynamic-ci-filter action asks for a verdict per job and emits it as a job output you gate on — a near drop-in replacement for dorny/paths-filter.

How a verdict is reached

Each verdict combines independent signals, and every signal’s contribution is printed in the job logs and the job summary, so a skip is always auditable. Signals are per-organization. Nothing is pooled across customers — your recommendations are derived only from your own CI history. Pass a comma-separated list to ignore-signals to drop any of them from the tally.

Set it up

1

Get your organization API token

In the Trunk app, go to Settings → Manage Organization → Organization API Token, and store it as a repository secret (for example TRUNK_API_TOKEN).
2

Add the filter job

One upstream job asks for verdicts for the whole workflow. Per-job outputs are set at runtime, so re-export each one you intend to gate on by name.
3

Gate your jobs on the output

Jobs are addressed by their key — what the job is written as under jobs:, and what github.job reports — not the name: it displays under. A display name changes with a job’s matrix values; the key does not. This is pre-job mode, and it captures the most savings because a skipped job never boots a runner. There is also a pre-step mode, where a job asks only about itself using the job-keys input; it is simpler to adopt but the runner has already started by the time the verdict arrives.

Always write != 'false'

Gate on != 'false', never on == 'true'.
A job with no verdict — a service outage, a job Trunk has not seen before, a job whose key it has not resolved yet — emits no output at all. != 'false' correctly runs that job. == 'true' would silently skip your entire test suite the first time anything went wrong. That convention is what makes the action’s fail-open behavior work. A transport error, a non-2xx response, a malformed response, or a request past the latency budget (30s by default) all resolve to “run”, the step itself never fails, and every fail-open is logged as a warning annotation and written to the job summary — so a real skip is always distinguishable from a degraded one.

Merge queues

A merge queue validates the exact commit about to land, so a skip there could merge untested code. Trunk therefore never skips a job on a Trunk Merge Queue branch: a request on a trunk-merge/ branch short-circuits to run-everything before any recommendation work happens. You do not need to gate the queue yourself, and in particular you should not add github.event_name != 'pull_request' — with draft merge-queue pull requests enabled, queue batches arrive as pull_request events on trunk-merge/* branches, so the event name cannot tell them apart from real PRs. Other merge queues are not covered. GitHub’s native merge queue validates on gh-readonly-queue/* branches via merge_group events, which are treated like any other request, so gate those yourself:

Feedback

Dynamic CI is being tuned against real repositories during the beta, and the fastest way to change it is to tell us what it got wrong. Reach out on Slack.