Using Hawk

August 21, 2026 ยท View on GitHub

Hawk analyzes public declarations in workspace library crates against configured production targets and workspace non-production targets. This guide covers invoking the tool; see Configuration for the hawk.toml reference and Architecture for the analysis model.

Install a prebuilt release

Hawk is pinned to Rust 1.98.0 and uses rustc_private. A prebuilt release still requires the exact normal Rust toolchain, but it does not require rustc-dev, RUSTC_BOOTSTRAP, or a source build:

rustup toolchain install 1.98.0

Install the latest release with the standalone shell installer:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/astral-sh/hawk/releases/latest/download/cargo-hawk-installer.sh | sh

The installer places cargo-hawk and cargo-hawk-driver on your PATH in the same directory. You can instead download the archive for your platform from GitHub Releases and place both executables on your PATH manually.

Run the Cargo subcommand with the pinned toolchain:

cargo +1.98.0 hawk check --manifest-path /path/to/workspace/Cargo.toml

Build Hawk

Hawk is pinned to Rust 1.98.0 and uses rustc_private; the repository toolchain configuration installs rustc-dev when necessary. A source build produces a cargo-hawk frontend and a cargo-hawk-driver compiler wrapper.

cargo build

Configure production targets

Without a hawk.toml, Hawk treats every workspace binary as a production target. To select specific binaries or audit internal libraries, declare the desired production targets in hawk.toml at the root of the workspace being analyzed:

[[production]]
package = "app"
bin = "app"
reason = "shipped application binary"

Every configured package and binary must be a target of that workspace. Once a configuration file exists, its production targets are authoritative: an API used only by an omitted binary can be reported as unnecessary or dead. See Configuration for multiple binaries, target-scoped entries, and accepted findings.

Run analysis

Run cargo hawk without a subcommand to see the available commands. Use check to analyze a workspace:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml

Configured production targets and workspace non-production targets are analyzed under --all-features --locked on the host target by default. A [[feature-profile]] matrix in hawk.toml can replace that single feature selection; Hawk unions evidence from every profile before producing diagnostics. The non-production surface includes tests, benches, examples, and compile-only doctests, which can be restricted to explicit packages with [[doctest]] entries. Diagnostics apply to workspace library crates compiled for those targets, including declarations enabled only under cfg(test).

Workspace libraries are treated as internal unless exempted. Exclude a library crate whose public API is consumed outside the configured production targets:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  --exclude-crate supported_library

Instrumented Cargo artifacts are reused under cargo-hawk-target/<workspace-name>-<path-hash> in the platform temporary directory by default. Including the workspace path prevents same-named checkouts and worktrees from sharing Cargo locks and artifacts. Use --target-dir to override that location and --graph-dir to retain serialized compiler fragments for investigation. Diagnostics are colored automatically in a terminal; use --color=always or --color=never to override terminal detection.

Use --output-format=json for a machine-readable diagnostic report. For a completed analysis, Cargo progress and compiler output remain on stderr, so stdout contains exactly one JSON object. Operational failures are reported on stderr and do not produce a JSON report:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  --output-format=json > hawk-report.json

The JSON report is versioned with schema_version (currently 5); breaking schema changes increment this version. Its summary describes the compilation target, configured production binaries or libraries, feature profiles, non-production coverage, and emitted diagnostic count. Every entry in diagnostics includes its category, lint code, and severity. Finding entries additionally include their finding kind, semantic identity (package, crate, item, definition kind, parent, and module scope), target-independent source-qualified identity (identity.id), compiler identity (identity.compiler_id), available source and expansion locations, and the test_only and test_compiled_only flags. Source locations include line, column, end_line, end_column, byte_start, and byte_end when a complete declaration range is available. Lines and columns are one-based, columns count Unicode scalar values, and byte offsets are zero-based UTF-8 offsets into the original source file. end_line/end_column and byte_end are exclusive; ranges include source-spanned attributes, documentation, and trailing field, variant, or re-export separators. The stable identity uses versioned, length-prefixed package, crate, item, definition kind, and source-location components, so cfg and path alternatives remain distinct while the same declaration can be correlated across targets; the compiler identity can change with the target or feature set. When rustc cannot retain a complete range for a parsed attribute, such as #[cold] or #[unsafe(link_section = "...")], the location intentionally falls back to file, line, and column; ending locations and byte offsets are omitted. Configuration diagnostics identify either the referenced lint and item or the excluded scope, along with the configuration location and reason.

--fix supports the default profile or one explicitly configured feature profile. Hawk rejects fixing runs with a multi-profile matrix; run analysis without --fix to review the combined findings.

Enforce diagnostics

Hawk reports diagnostics as warnings by default, so it can be introduced without changing build status. Deny the warnings group to use Hawk as a CI gate:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  -D warnings

Hawk accepts Clippy-style ordered -A/--allow, -W/--warn, and -D/--deny lint levels. Later options take precedence:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  -D warnings \
  -W hawk::unnecessary_public

The supported selectors are warnings, hawk::dead_public, hawk::unnecessary_public, hawk::unnecessary_restricted_visibility, hawk::unnecessary_crate_visibility, hawk::unknown_item, hawk::ambiguous_item, and hawk::unfulfilled_expectation. Denied diagnostics are emitted as errors and cause a non-zero exit status. Invalid configuration and failed instrumented Cargo builds fail independently of lint levels.

To focus on deletion candidates in either text or JSON output, pass --only dead-public. This suppresses visibility-reduction findings while preserving configuration diagnostics such as unknown items and unfulfilled expectations:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  --only dead-public

In text output, the final summary groups emitted findings by lint and Cargo package. Hawk reports configuration diagnostics separately under configuration.

hawk::unnecessary_crate_visibility is allow-by-default because preferring pub(super) over pub(crate) is a style choice. Enable it explicitly with -W hawk::unnecessary_crate_visibility or -D hawk::unnecessary_crate_visibility. The warnings group does not enable allow-by-default lints.

Apply fixes

Pass --fix to apply visibility reductions through Cargo's fix machinery:

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  --fix

Hawk emits machine-applicable suggestions for enabled, unsuppressed visibility findings. hawk::unnecessary_public reduces pub to pub(crate). hawk::unnecessary_restricted_visibility removes an explicit restricted visibility modifier when the item can be private. hawk::unnecessary_crate_visibility optionally reduces pub(crate) to pub(super). hawk::dead_public remains report-only because a visibility-only edit can activate rustc's dead_code lint; removing dead surface may require editing its remaining internal uses. Hawk delegates edit application and validation to cargo fix, including Cargo's source-control safety checks; pass --allow-dirty, --allow-staged, or --allow-no-vcs with --fix when the corresponding Cargo override is appropriate.

Fixes are limited to workspace library packages in the configured production or non-production surface. Hawk rechecks configured production targets and non-production targets, including compile-only doctests, after applying edits. Dead declarations and enum variants remain report-only.

Analyze another target

Pass --target TRIPLE to analyze another compilation target. Hawk forwards the target to Cargo but does not install a target SDK or configure a cross linker. When --target is omitted, Hawk explicitly analyzes the host target; this overrides Cargo's build.target configuration and CARGO_BUILD_TARGET so compilation, target-scoped configuration, and reported coverage agree.

For example, a macOS host can analyze Windows MSVC production targets using cargo-xwin. From the Hawk checkout, prepare the pinned toolchain once:

rustup target add x86_64-pc-windows-msvc
rustup component add llvm-tools-preview
cargo install cargo-xwin --locked

Then export the linker and Windows SDK configuration for Hawk's child Cargo process before running the analysis:

target=x86_64-pc-windows-msvc
eval "$(cargo xwin env --quiet \
  --target "$target" \
  --manifest-path /path/to/workspace/Cargo.toml)"

./target/debug/cargo-hawk check \
  --manifest-path /path/to/workspace/Cargo.toml \
  --target "$target"

Target-scoped production entries and expectations can keep platform-specific surfaces explicit; see Configuration.