setup-trunk · git:20261101.9b92b24 · 2026-11-01 · sha256 2f57ca1b53c52df7
setup-trunk git:20261101.9b92b24A
Immutable. This exact content never changes and is served at /api/v1/blob/2f57ca1b53c52df7.
---
name: setup-trunk
description: Use when integrating trunk.io into a repository for the first time, or when configuring trunk linters, git hooks, and pre-commit/push validation in an existing codebase with legacy lint violations.
metadata:
category: commits-ci-release
---
# Setup Trunk
## Overview
Trunk.io is a meta-linter and git hook manager.
Its core principle is "hold-the-line": existing violations are acknowledged and ignored initially, but new violations on changed files are blocked.
This lets teams adopt linting incrementally without a big-bang cleanup.
This skill covers first-time integration and configuration. Running trunk day to day is a different job — `trunk check` / `trunk fmt` at milestones, cache corruption, aligning trunk-pinned linter versions with the workspace, vulnerability triage — and is out of scope here.
## Initialization Flow
### 1. Inspect the repository before selecting linters
Do not start from a memorized linter bundle.
First inspect the repository's manifests and lockfiles, existing CI workflows, declared package scripts, language and generated-file layout, and any existing linter configuration.
Use that evidence to identify tools the project already runs, file types that actually exist, and checks CI already owns.
```bash
git ls-files | grep -E '(^|/)(package.json|pubspec.yaml|pyproject.toml|Cargo.toml|go.mod|Podfile)$'
git ls-files '.github/**'
git ls-files | grep -Ei '(^|/)([^/]*(eslint|prettier|ruff|markdownlint|shellcheck)[^/]*)$'
```
Read the files found, including scripts in manifests and the commands used by CI, before proposing `lint.enabled`.
Enable a linter only when a repository artifact or an explicit user requirement supports it; do not infer that mobile, web, infrastructure, and documentation checks all belong in the same repository.
### 2. Run trunk init
```bash
trunk init
```
The wizard prompts for:
1. Account connection (optional for local-only use)
2. Linter auto-detection based on files in the repo
3. Git hooks setup (built-in actions)
4. Initial full-repo scan to snapshot existing violations
**For open-source repos where you contribute a PR to someone else's codebase and don't want to commit trunk config:**
```bash
trunk init # then: trunk config hide
```
`trunk config hide` adds `.trunk/` to `.gitignore`, keeping the config local.
Alternatively, `trunk init --single-player-mode` does both in one step (verified in `trunk init --help`: "initialize trunk with a gitignored config, run 'trunk config share' to undo").
**A brand-new repository needs one commit before init.**
Detection reads committed files, so on a repository with no commit `trunk init` prints `No committed files in repo, unable to analyze files.` and exits 1.
Commit the initial files first.
In a repository scaffolded this session, that commit is a precondition of the setup, not a separate request.
**`-n` is not a safe way to make init non-interactive.**
`trunk init -n` answers no to every prompt, so it enables 0 linters, disables `trunk-announce`, `trunk-check-pre-push` and `trunk-fmt-pre-commit`, and installs no git hooks.
Pair it with the explicit `trunk check enable` and `trunk actions enable` calls below, or the run leaves an empty gate that still reports success.
(Both behaviors measured with trunk 1.25.0 on 2026-10-25.)
### 3. Review built-in actions
After `trunk init`, review which built-in actions are active and compare them with existing CI and repository hooks:
```bash
trunk actions list
```
Available actions commonly include:
| Action | Purpose |
| ------------------------- | --------------------------------------- |
| `trunk-announce` | Notifies about trunk updates |
| `trunk-check-pre-push` | Runs `trunk check` before `git push` |
| `trunk-fmt-pre-commit` | Auto-formats staged files before commit |
| `trunk-upgrade-available` | Notifies when upgrades are available |
Enable only actions that do not duplicate or conflict with the repository's existing hooks:
```bash
trunk actions enable trunk-fmt-pre-commit
trunk actions enable trunk-check-pre-push
```
**Disable `trunk-fmt-pre-commit` only** when contributing to an external repo that owns its own formatting rules (e.g., submitting a PR to an open-source project).
Re-enable when back on your own repo.
**In a checkout you do not control, read the actions before you install the hooks.**
`actions.definitions[].run` holds arbitrary shell, and a definition can bind it to `git_hooks: [pre-commit]`, exactly as the Flutter `dart-actions` example below does.
Installing the hooks and then exercising them with a commit runs that repository's shell as you.
So read `actions.definitions` and `actions.enabled` in the repository's own `trunk.yaml` first, approve each custom entry by name, and on a checkout you do not trust skip both the hook sync and the probe commit below.
The built-in review above covers trunk's own actions only.
**Enabling an action does not install the git hooks.**
`trunk actions enable` only writes the action into `trunk.yaml`.
Run `trunk git-hooks sync` to install them, then read `git config core.hooksPath`: trunk points it at its own cache and leaves `.git/hooks` holding nothing but samples.
An empty `.git/hooks` is therefore not evidence that the gate is missing, and `trunk git-hooks sync` prints `Trunk is managing your git hooks` whether or not anything changed.
**Prove the hook with a commit, not by running it.**
Executing the hook script from `core.hooksPath` by hand prints nothing and formats nothing, so it cannot tell you the gate works.
Stage a file carrying one fixable defect, commit it, and read the committed blob back.
(trunk 1.25.0, 2026-10-25: a staged `*emphasis*` with trailing spaces was committed as `_emphasis_` with none.)
### 4. Full-repo scan and baseline
```bash
trunk check --all
```
This identifies **all** existing violations.
Existing violations in the repo are expected to be fixed — "already enabled but ignored" lint rules are in scope.
Do not auto-fix the whole repository as part of setup without explicit approval.
That is a broad rewrite, so list the affected paths first and ask for approval for those explicit paths or a narrow linter filter.
After approval, run a scoped fix such as:
```bash
trunk check --fix path/to/file
trunk check --fix --filter=eslint src/
```
To focus on one linter at a time when overwhelmed:
```bash
trunk check --all --filter=eslint
trunk check --all --filter=shellcheck --sample=5
```
**Read how CI runs trunk before you rely on hold-the-line.**
If a workflow runs `trunk-io/trunk-action` with `check-mode: all`, CI checks every file, not only the changed ones.
Then a linter that you enable must report nothing on the whole repository, or the first PR after the change fails.
Run `trunk check --all --no-fix --show-existing` in the tree that you will push, without `--filter`, as the last step.
A filtered run or a run in a different checkout does not show the state that CI reads.
### Try a linter before you enable it
`--filter` runs only linters that are enabled.
For a linter in `lint.disabled`, or one that no section names, the check runs nothing and exits 2:
```log
✖ [--filter] 'checkov,hadolint,shellcheck,taplo' are not enabled
```
To try such a linter:
1. Copy `.trunk/trunk.yaml` to a scratch path.
2. Run `trunk check enable <linter> ...`.
3. Run `trunk check --all --no-fix --show-existing --filter=<linter>,...`.
4. Keep each linter that the evidence supports, and remove the others from `lint.enabled`.
`trunk check enable` can also write a config file into `.trunk/configs/`, for example `.shellcheckrc` or `.hadolint.yaml`.
Read each new file, and delete the files of linters that you do not keep.
`trunk check enable` puts the new entries at the top of `lint.enabled`, before the entries that were already there.
Sort the entries that you added.
(trunk 1.25.0, plugins v1.11.0, 2026-11-01: one call put `checkov`, `hadolint`, `shellcheck` and `taplo` before `actionlint`, and a later call put `cspell` before all of them.)
## Evidence-based Linter Selection
### No universal baseline
No linter is mandatory for every repository.
Choose from the following candidates only when the inspection step finds matching files, dependencies, configuration, or CI behavior:
| Evidence | Candidate linters |
| ------------------------------------------------------ | -------------------------------------------------------------------------- |
| GitHub Actions workflows | `actionlint` |
| Shell scripts | `shellcheck`, optionally `shfmt` when the repository already formats shell |
| JavaScript or TypeScript manifest and existing config | project-pinned `eslint@SYSTEM`, `prettier` |
| Python manifest and existing config | `ruff`, `mypy`, or `black` as declared by the project |
| Markdown or YAML with an established style config | `markdownlint`, `yamllint` |
| Dependency lockfiles supported by the scanner | `osv-scanner` after verifying the lockfile targets it can interpret |
| Terraform, Kubernetes, or other IaC files | `checkov` |
| A repository requirement for committed-secret scanning | `trufflehog` |
The stack examples below are reference fragments, not bundles to copy wholesale.
Remove every entry unsupported by the inspected repository and preserve tools already pinned by the project.
### React Native
```yaml
lint:
enabled:
- actionlint # GitHub Actions workflow validation
- checkov
- dotenv-linter # .env file hygiene
- eslint # JS/TS bridge code
- git-diff-check
- grype # container/binary vulnerability scan
- ktlint # Android/Kotlin
- markdownlint
- osv-scanner
- prettier
- shellcheck # shell scripts
- shfmt # shell formatter
- swiftformat # iOS/Swift (or swiftlint)
- trufflehog
- yamllint
ignore:
- linters: [shellcheck]
paths: ["android/gradlew", "example/android/gradlew"]
- linters: [markdownlint]
paths: [".github/**/*.md"]
- linters: [dotenv-linter]
paths:
- "ios/.xcode.env" # uses 'export VAR=VALUE' — dotenv-linter rewrites to 'VAR=VALUE', breaking Xcode
- "ios/.xcode.env.local"
```
### Flutter
```yaml
lint:
disabled:
- dart # trunk's built-in dart linter conflicts with project SDK; use custom action instead
enabled:
- actionlint
- checkov
- git-diff-check
- ktlint # Android/Kotlin
- markdownlint
- osv-scanner
- oxipng # PNG optimizer (review output before trusting)
- prettier
- shellcheck
- shfmt
- swiftformat # iOS/Swift
- trufflehog
- yamllint
ignore:
- linters: [shellcheck]
paths: ["android/gradlew"]
- linters: [markdownlint]
paths: [".github/**/*.md"]
# Custom dart tooling via actions (replaces trunk's dart linter):
actions:
definitions:
- id: dart-actions
description: Run 'dart run import_sorter:main' on pre-commit
display_name: Dart Import Sorter Pre-Commit Hook
run: |
#!/usr/bin/env bash
set -euo pipefail
if dart run import_sorter:main -e | grep -qE 'Sorted [1-9]'; then
exit 1
else
exit 0
fi
packages_file: pubspec.yaml
triggers:
- git_hooks: [pre-commit]
enabled:
- dart-actions
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
- trunk-upgrade-available
```
### Python
```yaml
lint:
disabled:
- bandit # high false-positive rate; use ruff's S rules instead
- isort # replaced by ruff's I rules
enabled:
- actionlint
- black # formatter
- checkov
- git-diff-check
- markdownlint
- mypy # type checking
- osv-scanner
- prettier
- ruff # fast linter (replaces flake8/isort/bandit)
- trufflehog
- yamllint
```
### Markdown: seed the Prettier-compatible baseline
When `markdownlint` is enabled next to `prettier`, replace the `.trunk/configs/.markdownlint.yaml` that `trunk init` wrote with the baseline gist, so every repository starts from the same file instead of a remembered rule list:
```bash
seed=$(mktemp) && gh gist view d34ab86436fc18aa750f21818c1abe48 -f .markdownlint.yaml --raw > "$seed" && mv "$seed" .trunk/configs/.markdownlint.yaml
trunk check --no-fix --filter=markdownlint --sample=5
```
Fetch into a temporary file outside the checkout and move it into place only on success.
A plain `> .trunk/configs/.markdownlint.yaml` truncates the existing config before `gh` runs, so an unauthenticated CLI or a failed request leaves an empty file that the next check silently accepts.
A predictable name inside the checkout, such as `.markdownlint.yaml.new`, can be a tracked symlink that redirects the write to any file you can modify.
With this form a failed fetch leaves the old config untouched and an empty temporary file.
On a checkout you do not control, confirm that `.trunk/configs` is a real directory before the `mv`.
The gist is a living baseline, so record which revision was seeded in the commit that adds the file: `gh api gists/d34ab86436fc18aa750f21818c1abe48 --jq '.history[0].version'`.
Without the GitHub CLI, fetch the raw file from `https://gist.githubusercontent.com/AndrewDongminYoo/d34ab86436fc18aa750f21818c1abe48/raw/.markdownlint.yaml` the same way.
The baseline turns off only the rules Prettier already enforces — whitespace, indentation, blank lines, line length, and heading, list, fence, and emphasis style — and leaves every content rule on, `MD040` included.
Relax a content rule only per repository, with the reason written beside it.
An existing backlog is `markdownlint-triage`'s job, not a reason to turn rules off here.
A table whose pipes are not aligned reports `markdownlint/MD060` on every row.
Prettier aligns them, so run `trunk fmt` on the file before you treat `MD060` as a gap in this baseline.
### Shell: replace the generated `enable=all`
`trunk check enable shellcheck` writes a `.trunk/configs/.shellcheckrc` that sets `enable=all`, which turns on shellcheck's optional style checks.
On one 163-line bash script that produced 100 findings, every one of them `low` (`SC2250` on brace style, `SC2292` on `[[ ]]`), which hides real defects and teaches the reader to skip the gate.
Use `severity=info` alone, and the same script reports nothing.
Do not reach for a repository-wide `disable=SC1087` to get there.
`SC1087` is an `error`, so no `severity` setting filters it, and it reports a real defect rather than a style preference: `"$arr[@]"` expands to the first element followed by a literal `[@]`.
Measured on the same 163-line script, `severity=info` without that disable reports 0 and exits 0, while `enable=all` reports 100 and exits 1.
Suppress `SC1087` at the one line that needs it, after confirming the brackets are meant literally.
(Measured with shellcheck 0.11.0 on 2026-10-25.)
Neither shellcheck nor shfmt parses zsh.
A `#!/bin/zsh` script reports `shellcheck/SC1071` at `high`, and `shfmt` would rewrite it as bash.
Prefer moving the script to `#!/usr/bin/env bash`.
Ignore the path for both linters only when the zsh shebang is deliberate, and say so in a comment beside the ignore.
### Next.js / TypeScript
```yaml
lint:
enabled:
- actionlint
- checkov
- dotenv-linter
- eslint # use @SYSTEM if repo pins its own eslint version
- git-diff-check
- markdownlint
- osv-scanner
- oxipng # PNG assets
- prettier
- svgo # SVG assets — keep enabled for web, disable for mobile
- trufflehog
- yamllint
```
## Handling False Positives and Destructive Linters
### Known problematic combinations
| Linter | Problematic target | Action |
| ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `oxipng`, `svgo` | SVG/PNG assets managed externally or with metadata | Disable unless you've reviewed output |
| `svgo` | Mobile (React Native/Flutter) repos with SVGs | Disable — rarely useful, high risk |
| `shellcheck` | `android/gradlew`, `example/android/gradlew` | Ignore only after confirming the generated wrapper is intentionally outside shell lint scope |
| `osv-scanner` | Resolved dependency lockfiles | Scan supported targets directly; ignore only a proven unsupported or mis-mapped target |
| `markdownlint` | `.github/**/*.md` (PR templates, issue templates) | Ignore — not user-authored prose |
| `dart` | Flutter projects using a pinned SDK | Disable; use `custom action` instead |
| `dotenv-linter` | `ios/.xcode.env`, `ios/.xcode.env.local` | Ignore — Xcode requires `export VAR=VALUE`; dotenv-linter strips `export`, breaking Xcode builds |
| `cspell` | `android/gradlew` (auto-generated identifiers) | Add to ignore alongside shellcheck |
| Any linter | Config files in non-root dirs (`.github/`, etc.) | Check if linter respects non-root path |
**Warning:** `oxipng`, `svgo`, and `dotenv-linter` modify files in place.
`oxipng`/`svgo` can corrupt binary assets; `dotenv-linter` silently rewrites `.env` syntax that platform tooling depends on (e.g., Xcode requires `export KEY=VALUE` but dotenv-linter normalizes it to `KEY=VALUE`).
Always verify output on first run.
### Linters that use the repo's own tooling (version mismatch false positives)
Some linters invoke the repo's own binary (eslint via `node_modules`, dart via the project's SDK).
If trunk's managed version differs from the project's, it may apply different rules or miss plugins.
**Affected linters:** `eslint`, `dart`
**Symptom:** `trunk check` reports violations that don't appear when running the tool directly.
**Fix:**
```yaml
lint:
enabled:
- eslint@SYSTEM # use node_modules/.bin/eslint
- dart@SYSTEM # use project's dart SDK on PATH
```
`SYSTEM` tells trunk to skip its sandboxed version and invoke the repo's binary.
Do **not** use `SYSTEM` if the tool isn't installed in the project.
### Changing a linter's commands: override its definition
To turn on a command that a plugin ships disabled, or to change one field of a command, override the linter's definition by name under `lint.definitions`.
This is the recommended form, because `lint.enabled` stays a plain list of `linter@version` strings:
```yaml
lint:
definitions:
- name: pinact
commands:
- name: upgrade
enabled: true
enabled:
- pinact@<version>
```
Trunk merges the override onto the plugin's definition by linter name, and each command by its `name`, so only the fields you write change.
The trunk-io/plugins `pinact` definition ships `upgrade` with `enabled: false`; with the override above, `trunk print-config` shows that command with the plugin's `run`, `output` and `success_codes` unchanged and `enabled: true` added (checked 2026-10-30).
Avoid attaching settings to an entry of `lint.enabled`:
```yaml
lint:
enabled:
- pinact@<version>:
commands: [lint, upgrade]
```
That form turns one list item into a mapping, so `lint.enabled` mixes strings and objects, and the change hides in the list of enabled linters instead of in `lint.definitions`.
After either edit, run `trunk print-config` and read the merged command before you trust it.
### Configuring ignores in `.trunk/trunk.yaml`
Keep format and style ignores linter-specific.
Never place resolved dependency lockfiles under `[ALL]`: vulnerability scanners need to inspect supported resolved locks directly.
Ignore a scanner target only after direct evidence shows that exact target is unsupported or mis-mapped, and record the rationale beside the scanner-specific ignore.
```yaml
lint:
ignore:
- linters: [ALL]
paths:
- "src/generated/**" # generated code
- "!src/generated/**/*.ts" # re-enable for TS inside generated/
- linters: [shellcheck, cspell]
paths:
- "android/gradlew"
- "example/android/gradlew"
- linters: [markdownlint]
paths:
- ".github/**/*.md"
- linters: [svgo, oxipng]
paths:
- "**" # disable entirely for mobile repos
```
When the linter reads its own ignore file, put the exemption there instead of in `lint.ignore`.
A trunk ignore applies only inside trunk.
A direct run of the same tool, or another scanner, still reports the path.
For example, `pinact` exempts floating refs such as `dtolnay/rust-toolchain@stable` with a rule in `.github/pinact.yml` (or `.pinact.yaml`):
```yaml
version: 3
rules:
- ignore: true
conditions:
- expr: |
ActionName == "dtolnay/rust-toolchain" && ActionVersion == "stable"
```
Match on `ActionName`, which includes the owner.
`ActionRepoOwner` for that action is `dtolnay`, not `rust-toolchain`.
A whole-file `lint.ignore` for pinact also stops pinact from checking the pinnable actions in that file.
A direct `pinact run --update` without a token hits the GitHub API limit of 60 requests and prints `403 API rate limit` for each action, so run it with `GITHUB_TOKEN="$(gh auth token)"` before you read its result as "up to date".
(pinact 4.1.1 through trunk and 5.0.2 direct, 2026-11-01.)
### Lockfiles that trunk's `osv-scanner` does not scan
Trunk runs `osv-scanner` only on files of its built-in `lockfile` type, and that type has no `uv.lock`.
So `trunk check` never scans a Python project that uv locks, and it reports no error.
`osv-scanner` itself reads `uv.lock`.
Add a file type and override the linter's `files`:
```yaml
lint:
definitions:
- name: osv-scanner
files: [lockfile, uv-lockfile]
files:
- name: uv-lockfile
filenames: [uv.lock]
```
An override of `files` replaces the plugin's list, so keep `lockfile` in it.
Confirm the change with `trunk check --all --no-fix --show-existing --filter=osv-scanner`: the `Checked N files` count goes up by one for each `uv.lock`.
For any other lockfile, compare its name with the `lockfile` type in trunk-io/plugins `linters/plugin.yaml` before you trust a clean scan.
(trunk 1.25.0, plugins v1.11.0, osv-scanner 2.7.0, 2026-11-01.)
### A workspace in a subdirectory (monorepo)
Use this when a JavaScript workspace lives in a subdirectory, such as `frontend/`, and the root `.trunk/trunk.yaml` lints it.
This also applies when a separate repository was merged in with its own `.trunk/`.
To merge a nested `<dir>/.trunk/` into the root config:
1. Read `<dir>/.trunk/trunk.yaml` and note each linter it enabled, each `lint.definitions` override, each `lint.files` type, each `lint.ignore` entry, each `tools.definitions` entry and each action.
2. Diff each file in `<dir>/.trunk/configs/` against the root `.trunk/configs/`.
3. Migrate what the root does not have yet:
- Add each linter that only `<dir>/` enabled to the root `lint.enabled`. If it applies only to `<dir>/`, limit it with a `lint.ignore` entry, as the eslint example below does.
- Copy each definition override, `tools.definitions` entry, ignore entry and action that still applies. Rewrite its paths relative to the repository root.
- Copy each `lint.files` type that a copied override names and the root does not define, or the override points at a type that no longer exists.
- For each config file that differs, decide which rules apply to `<dir>/`. Merge them into the root copy. Keep a separate copy inside `<dir>/` only for a tool that reads the nearest config file, such as prettier.
4. Delete `<dir>/.trunk/`.
5. Remove the root `lint.ignore` entry for `<dir>/**`.
6. Run `trunk check --all --no-fix --show-existing --filter=<linter>` for each linter, and read the results under `<dir>/`.
Trunk runs some linters in a sandbox directory that does not contain `<dir>/node_modules`.
Two linters behave differently there:
- **prettier with a plugin.**
If `<dir>/.prettierrc.*` names a plugin, trunk's prettier fails on every file under `<dir>/` with `Cannot find package '<plugin>' imported from <sandbox>/noop.js`.
Install the plugin with trunk's prettier tool:
```yaml
tools:
definitions:
- name: prettier
extra_packages:
- prettier-plugin-tailwindcss@0.8.1 # keep equal to <dir>/package.json
```
Then compare `trunk check --all --no-fix --show-existing --filter=prettier` with `pnpm exec prettier --check .` in `<dir>/`. They must list the same files.
Prettier uses the nearest config file, so files outside `<dir>/` keep the defaults or the root config.
- **eslint with a flat config in `<dir>/`.**
trunk's eslint loaded `<dir>/eslint.config.mjs` and its plugins from `<dir>/node_modules`.
For files outside `<dir>/`, it fails with `ESLint couldn't find an eslint.config.* file`.
Limit eslint to the workspace:
```yaml
lint:
ignore:
- linters: [eslint]
paths:
- "**"
- "!<dir>/**"
```
Keep the trunk eslint version equal to the one in `<dir>/package.json`.
This works only after the package manager installed `<dir>/node_modules`.
To prove that the config is read, add a file with a known violation, confirm that trunk reports it, and then delete the file.
(trunk 1.25.0, prettier 3.9.10, eslint 10.14.0, 2026-11-01.)
### Linter config files in subdirectories
If a linter's config file lives in `.github/` or another non-root directory (e.g., `.cspell.json` in `.github/`), trunk may not detect it and will report false positives.
Options:
- Move the config to the repo root, OR
- Add a root-level config that references the subdirectory file, OR
- Suppress the linter for affected paths in `lint.ignore`
## Pre-commit / Pre-push Validation Workflow
Trunk checks **only changed files** by default (no `--all`).
This is intentional — run without `--all` before committing:
```bash
trunk check
trunk fmt path/to/changed-file
```
If a violation is **intentional** (accepted technical debt, unavoidable pattern), suppress it inline:
<!-- trunk-ignore-all(trunk/ignore-unknown-linter) -->
```bash
# Single line
some_code # trunk-ignore(linter-name/rule-id): reason
# Block
# trunk-ignore-begin(linter-name)
legacy_code_block()
# trunk-ignore-end(linter-name)
# Entire file
# trunk-ignore-all(linter-name)
```
**Only commit when `trunk check` exits clean** (or all remaining issues have explicit `trunk-ignore` annotations).
### Vulnerability findings
When `trunk check` reports a dependency vulnerability (e.g., from `osv-scanner`):
1. Scan the actual resolved lockfile directly with the scanner and confirm the advisory maps to the resolved dependency path and target.
2. Assess impact scope — is the vulnerable code path reachable?
3. If reachable, upgrade the dependency or pin to a safe version. If no upstream fix exists, file an issue with the upstream project.
4. If not reachable, document the reachability decision without hiding the resolved lockfile from unrelated scanners.
5. Ignore a scanner target only after direct evidence shows that exact target is unsupported or mis-mapped. Keep the ignore scanner-specific and record the evidence and rationale.
## Common Mistakes
| Mistake | Fix |
| ------------------------------------------------------------- | -------------------------------------------------------------------- |
| Running `trunk check --all` every commit | Use `trunk check` (no `--all`) for daily work |
| Disabling `trunk-fmt-pre-commit` for convenience | Only disable for external repo contributions |
| Leaving `svgo` enabled in a mobile repo | Disable — SVG optimization rarely applies and can corrupt assets |
| Blanket-ignoring resolved lockfiles under `[ALL]` | Keep style ignores linter-specific and scan supported locks directly |
| Using trunk's `dart` linter in Flutter projects | Disable `dart`; use a custom `dart-actions` definition instead |
| eslint/dart reporting different results than running directly | Pin to `@SYSTEM` in `trunk.yaml` to use the repo's own binary |
| Forgetting `!path` negative globs | Use `!pattern` to re-enable a subset inside an ignored directory |
| Trusting a clean `osv-scanner` run in a uv project | Add a `uv.lock` file type; trunk's `lockfile` type omits it |
| Exempting a pinact floating ref with `lint.ignore` | Use a `rules` entry in `.github/pinact.yml` |
| Prettier plugin "Cannot find package" under a subdirectory | Add the plugin to the prettier tool's `extra_packages` |
## Linter configuration references
Read the linter's own config reference before you write its config file.
Read the trunk plugin definition before you override `files`, `commands` or `direct_configs`.
| Topic | Reference |
| ------------------------------------------------------- | -------------------------------------------------------------------- |
| `trunk.yaml` keys | <https://docs.trunk.io/reference/trunk-yaml> |
| Trunk linter definitions (`linters/<name>/plugin.yaml`) | <https://github.com/trunk-io/plugins/tree/main/linters> |
| Trunk file types such as `lockfile` | <https://github.com/trunk-io/plugins/blob/main/linters/plugin.yaml> |
| actionlint | <https://github.com/rhysd/actionlint/blob/main/docs/config.md> |
| eslint (flat config) | <https://eslint.org/docs/latest/use/configure/configuration-files> |
| markdownlint rules | <https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md> |
| osv-scanner | <https://google.github.io/osv-scanner/configuration/> |
| pinact | <https://github.com/suzuki-shunsuke/pinact/blob/main/docs/config.md> |
| prettier | <https://prettier.io/docs/configuration> |
| ruff | <https://docs.astral.sh/ruff/configuration/> |
| shellcheck (`.shellcheckrc`, see RC FILES) | <https://github.com/koalaman/shellcheck/blob/master/shellcheck.1.md> |
| yamllint | <https://yamllint.readthedocs.io/en/stable/configuration.html> |
## Quick Reference
Before inventing a config, look at one you already wrote. `find-trunk-repos` is on PATH whenever this plugin is enabled and lists your own public repositories whose default branch already carries `.trunk/trunk.yaml`, so an existing setup can be read rather than reconstructed. It needs the GitHub CLI authenticated (`gh auth login`), and prints `<owner/repo> <default-branch> <file-url>`.
```bash
find-trunk-repos # your repos that already have a trunk config
trunk init # Initialize trunk in repo
seed=$(mktemp) && gh gist view d34ab86436fc18aa750f21818c1abe48 -f .markdownlint.yaml --raw > "$seed" && mv "$seed" .trunk/configs/.markdownlint.yaml # seed the markdownlint baseline (after init)
trunk config hide # Keep .trunk/ local (not committed)
trunk config share # Commit .trunk/ config to repo
trunk actions list # View enabled/disabled actions
trunk actions enable <action> # Enable a built-in action
trunk check # Check changed files only
trunk check --all # Check entire repo (use at init)
trunk check --fix path/to/file # Fix an approved explicit path
trunk check --filter=<linter> # Scope to one linter
trunk fmt path/to/file # Format an explicit path
trunk check --sample=5 # Test linter config on 5 sample files
```