Auto-detection
When you omit provider (or set provider=auto), Clover infers the provider and its parameters from the content of the target line. Common cases need almost no annotation.
# @clover: constraint=minor
FROM redis:7.2.0
Here Clover recognizes a Docker image reference on the line and resolves it with the Docker provider, inferring repository=redis. A line that names a GitHub repository resolves with the GitHub provider instead.
The @clover shorthand
The canonical spelling of an auto marker is a bare @clover comment. The @ sigil stands for provider=auto, and the colon returns when other keys follow.
# @clover
FROM redis:7.2.0
# @clover: constraint=minor
FROM redis:7.2.0
The longhand clover: provider=auto means the same thing and stays valid. The shorthand is what clover annotate writes, and clover format rewrites the longhand into it. A redundant @clover: provider=<name> unfolds back to longhand, since an explicit provider always wins.
Recognized shapes
Auto-detection recognizes these shapes:
| Shape | File | Provider | Inference and notes |
|---|---|---|---|
uses: reference (commit SHA or tag) | GitHub Actions YAML | GitHub | repository |
FROM instruction (tag or digest) | Dockerfile, Containerfile | Docker | registry, repository |
image: mapping (tag or digest) | YAML | Docker | registry, repository |
uses: docker:// container job (tag or digest) | Workflow YAML | Docker | registry, repository |
Digest pin with a floating tag (nonroot, latest) | Any Docker shape above | Docker | Infers track, so the digest stays fresh while the tag text stays put |
component: include (gitlab.com/group/project/name@1.0.0) | GitLab CI/CD YAML | GitLab | repository, plus host on a self-managed instance |
terraform = "1.9.8" tool version | mise, asdf | HashiCorp | product |
node (or the mise alias nodejs) tool version | mise, asdf | Node.js | - |
go (or golang) tool version | mise, asdf | Go | - |
python tool version | mise, asdf | Python | - |
rust tool version | mise, asdf | Rust | - |
swift tool version | mise, asdf | Swift | - |
zig tool version | mise, asdf | Zig | - |
github: or ubi: backend, or a GitHub-released registry tool (tofu, ripgrep, …) | mise, asdf | GitHub | repository |
A registry tool released only on PyPI (ansible, yamllint, …) | mise, asdf | PyPI | package |
A registry tool released only on npm (prettier, wrangler, …) | mise, asdf | npm | package |
A registry tool released only on crates.io (magika, …) | mise, asdf | crates.io | package |
go and toolchain directives | go.mod | Go | Resolved from the go.dev download index |
.python-version pin (whole line) | pyenv | Python | No comment syntax, so tracked through a sidecar or clover run --infer. An implementation-prefixed pin like pypy3.10-7.3.12 infers nothing |
.swift-version pin (whole line) | swiftly | Swift | No comment syntax, so tracked through a sidecar or clover run --infer. A snapshot pin like 6.1-snapshot-2026-06-29 infers nothing |
target-version (compact py314 form) | pyproject.toml (ruff, black, mypy) | Python | - |
requires-python floor | pyproject.toml | Python | Bumped in place, so ">=3.14" keeps its operator and precision. A range like ">=3.10,<4" carries two tokens and is skipped as ambiguous |
Quoted dependency specifier ("uv_build>=0.8.24") | pyproject.toml dependencies, requires, or dependency-group array | PyPI | package. Bumped in place with any trailing environment marker left untouched. Only ==, >=, ~=, === are recognized, since bumping an exclusion (!=) or a cap (<, <=) would invert its meaning. A range (">=1.26,<2.1") or a multi-specifier line is skipped as ambiguous |
channel = "1.97.0" | rust-toolchain.toml | Rust | A named channel (stable, nightly-2026-07-11) carries no version and infers nothing |
rust-version floor | Cargo.toml | Rust | Bumped in place with precision preserved, so "1.70" advances only when a new minor line ships |
required_version constraint | .tf (Terraform) | HashiCorp | product=terraform, bumped in place so "~> 1.11.0" keeps its operator and precision. In a .tofu file it tracks OpenTofu through the GitHub provider instead. In .tf OpenTofu files, set provider explicitly |
version constraint in a required_providers entry | .tf (Terraform) | Terraform | Reads the sibling source by parsing the file as HCL, the one inference that looks beyond the target line. A version outside required_providers infers nothing. In a .tofu file it resolves with provider=opentofu against the OpenTofu registry. In .tf OpenTofu files, set provider=opentofu explicitly |
The mise rows cover any of the shapes mise reads (mise.toml, .mise.toml, a mise.local.toml or mise.<env>.toml variant, mise/config.toml, .config/mise/config.toml, a .config/mise/conf.d fragment) and asdf’s .tool-versions file, which mise reads with the same tool names. The tool maps are generated from the mise registry. A tool with a GitHub-shaped backend tracks its repository, and one released only through pipx, npm, or cargo tracks the package that ecosystem installs.
When to be explicit
Auto-detection covers the obvious cases. Set provider explicitly when:
- the line is ambiguous, or the value you want to track is not the most obvious token on it,
- you are tracking something that is not literally written on the target line,
- you want the annotation to document intent regardless of the line’s contents.
Inference only fills in what you leave out, and any key you set yourself always wins.
Generating annotations
To add @clover directives across an existing codebase rather than write them by hand, run clover annotate. It scans for the same lines auto-detection recognizes and inserts a directive above each, so onboarding a repository is a single command.
To skip annotations entirely, run clover run --infer: it updates every recognized line directly, as if each carried a bare @clover, writing no comments at all. Annotations remain the way to pin selection rules (constraint, include, cooldown, …) to a line.