Skip to content

Feature-gated (cpal) code is never type-checked: the gate and pre-push hook both build --no-default-features #1380

Description

@dc0sk

Found while fixing #1369, which edits run_drive — a shipped CLI subcommand behind
#[cfg(feature = "cpal-backend")].

The gap, measured rather than argued

Every routine check builds with --no-default-features, which switches cpal-backend off:

  • scripts/gate.sh → cargo clippy --workspace --no-default-features --all-targets and
    cargo test --workspace --no-default-features
  • .cargo-husky/hooks/pre-push:73,84 → the same two flags

So #[cfg(feature = "cpal-backend")] code is never type-checked by either.

Probe, run at 331ba2d5. A planted type error inside run_drive
(engine.this_method_does_not_exist(); — valid syntax, no such method):

GATE clippy --workspace --no-default-features --all-targets  -> rc=0   (invisible)
cargo check -p openpulse-cli --features cpal-backend         -> rc=101 (caught)
      error[E0599]: no method named `this_method_does_not_exist` found for struct `ModemEngine`

Scope it correctly — my first probe got this wrong. A syntax error IS caught (rc=101), because
#[cfg]-disabled code must still parse. What escapes is everything after parsing: type errors,
borrow errors, wrong arity, renamed methods. So the failure mode is not "garbage compiles" but
"the feature-gated path silently rots as the APIs under it change" — exactly the code most likely
to drift, since nobody compiles it while editing something else.

What is behind that gate

openpulse-cli's cpal-backend is on by default, so a developer running plain
cargo build -p openpulse-cli does compile it — which is why this has not bitten yet. It is the
automated checks that do not.

calibrate drive is the example at hand: it keys a real transmitter and is the operator's TX-drive
tuning tool. Its correctness matters more than most, and no automatic check compiles it.

CI does not close it either

ci.yml:324 has a Build cpal CLI step, but it is (a) inside jobs scoped to
startsWith(github.head_ref, 'release/') || workflow_dispatch (#1120/#1144), and (b) conditional on
steps.aloop.outputs.available == 'true' — snd-aloop being present on the runner, with a
::warning:: and a skip when it is not. So on a normal PR it never runs, and on a release it runs
only if the runner cooperates.

Options

  1. Add one cargo check --features cpal-backend step to gate.sh. Cheap — a check, not a test
    run, and only the crates that have the feature. It would have caught the planted error. Note
    openpulse-audio, openpulse-ardop, openpulse-kiss and openpulse-testbench have their own
    cpal features with the same exposure, so the step should cover the set rather than the CLI alone.
  2. Make the pre-push hook do the same for touched crates that carry a cpal feature.
  3. Accept it, and record in CLAUDE.md that feature-gated paths are developer-compiled only — the
    honest version of the status quo.

Related: #1144 (the gate does not run at merge), #1120 (why ci.yml is release-scoped). Distinct
from both: this is code that no configuration of the local gate compiles.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions