For clippy’s disallowed_macros lint, an #[allow(clippy::disallowed_macros)] placed at or around the call site (the macro invocation) is silently ineffective on newer clippy — the lint resolves at the lexical site where the banned macro tokens live, so the only robust placement is a module-root inner attribute #![allow(...)] at the file that defines the wrapper macro.

Core finding

mando bans tracing::error! and log::error! via clippy.toml (disallowed-macros) to force all error logging through the mando_core::error! wrapper macro. That wrapper must itself call tracing::error!, so it needs to suppress the lint for its own expansion. A per-invocation #[allow(clippy::disallowed_macros)] — on the tracing::error! call, on a wrapping #[allow] { tracing::error!(...) } block, or on the enclosing fn / match arm / let statement — is silently INEFFECTIVE on a newer clippy (probe reported clippy 1.95). The lint still fires, and clippy additionally warns the attribute is “unused, since it’s applied to a macro invocation”. The only placement that reliably works is a crate/module-root inner attribute #![allow(clippy::disallowed_macros)] at the top of the module that DEFINES the macro — i.e. mando-core/src/error.rs.

Why it behaves this way

disallowed_macros resolves the lint level at the lexical site where the banned macro tokens (tracing::error!) physically appear — which is the macro definition, not any call site. Consequences:

  • An #[allow] at the call site is the wrong scope entirely; the banned tokens aren’t lexically there, they arrive via expansion. Hence “unused attribute, applied to a macro invocation”.
  • One module-root #![allow(clippy::disallowed_macros)] in mando-core/src/error.rs (where tracing::error! is actually written, inside the error! wrapper) covers all cross-crate callers — mando-lib, mando-bess, etc. — because they expand the wrapper but never lexically contain tracing::error! themselves.

General lesson

For clippy lints that fire on macro expansions (disallowed_macros is the canonical example), #[allow] attributes attached at or around the expansion/call site frequently do not take effect. Prefer a module-root #![allow(...)] at the macro’s definition site. This is the version-independent, single-point placement.

For Agents

Rule of thumb: if a clippy lint fires because of what a macro expands into, put the #![allow] where the macro is defined, not where it’s called. Attaching #[allow] to a macro invocation (or its enclosing statement/fn/block) is a no-op for expansion-resolved lints and clippy will flag the attribute itself as unused.

Caveat — do not overstate

Toolchain-version dependence

The workspace pins toolchain 1.88.0 (rust-toolchain.toml). The wrapper macro currently on develop uses the block-level #[allow] form and presumably passes CI on 1.88 — so the block form may still work under the pinned clippy. The probe that exposed the ineffectiveness used a newer clippy (1.95). Takeaway: the block / per-invocation form is fragile across clippy versions; the module-root #![allow] is the robust placement that can’t be worse on 1.88 and is confirmed to work on newer clippy. Anyone touching this macro should verify clippy-clean under the pinned toolchain before relying on either form.

Placement summary

PlacementEffective?Notes
#[allow] on the tracing::error! invocationNo (on 1.95)Clippy warns: unused attribute on a macro invocation
#[allow] { tracing::error!(...) } wrapping blockNo (on 1.95)Same — lint resolves at definition site
#[allow] on enclosing fn / match arm / letNo (on 1.95)Same
#![allow(clippy::disallowed_macros)] at top of the defining module (mando-core/src/error.rs)YesCovers all cross-crate callers; version-independent

Where this lives in mando

  • Ban is declared in mando’s clippy.toml (disallowed-macros) — targets tracing::error! and log::error!.
  • The sanctioned wrapper is mando_core::error!, defined in mando-core/src/error.rs. That file is where the #![allow(clippy::disallowed_macros)] belongs.
  • Related macro plumbing for the error path is documented in BE-3117 Per-Flow Error Context Store (the record_error! macro falls back to mando_core::error! out of flow scope).
  • mando-lib-macro — proc-macro crate (#[derive(ErrorCode)]); the wider macro/error-code tooling surface.
  • BE-3117 Per-Flow Error Context Store — error-handling redesign during which this gotcha was found; record_error! calls mando_core::error! as its out-of-scope fallback.
  • Agent Context — dense agent reference for the Mando workspace (error handling = thiserror + #[derive(ErrorCode)]).
  • LOG — activity log.