← All decisions

Zero-unexpected-warnings gate: fixed the one real dead import, enforced the rest via a checked-in baseline diff, not a silent policy

warningstoolingprocessgleam-limitation

Context

User feedback: prior sessions in this conversation had noticed but not addressed ~48 gleam build/test warnings, and asked for a durable, architecturally-locked rule that all compiler warnings be resolved before any task is declared finished. Investigated every warning rather than assuming they were all the same kind of thing. Found exactly one real bug: test/glemy/pe_test.gleam imported gleam/float but no longer used it (a leftover from an earlier diagnostic test that was later simplified). All ~47 remaining warnings, on inspection, only ever occur on the Erlang target build -- gleam build --target javascript reports zero warnings, meaning every flagged import/private-function is genuinely used, just not by any Erlang-compiled code (since the code using them is @target(javascript)-gated).

Options considered

Restructure files to separate @target(javascript)-only code from dual-target code, hoping isolation avoids the warnings — rejected
Tested directly before rejecting, not assumed: io.gleam is already 100% @target(javascript)-gated (zero dual-target declarations, confirmed by reading the file) and still produces the exact same 'unused imported type'/'empty module' warnings on the Erlang build. Isolation doesn't help -- the warning is a property of the file existing in the Erlang build's analysis at all, not of how its declarations are mixed.
Suppress the warnings via Gleam's underscore-discard import aliasing (`import x as _x`) — rejected
Tried directly against this codebase's io.gleam: aliasing the whole module (`import glemy/pe/vector2.{type Vector2} as _vector2`) does silence the module-level warning, but the still-present unqualified `type Vector2` item import gets flagged on its own -- and Gleam requires type aliases to start with an uppercase letter (confirmed by the compiler's own parse error when `as _Vector2` was tried), which rules out the underscore-discard convention for `type` imports specifically. No combination tried eliminated the warning.
Check gleam.toml for a way to exclude specific files/directories from a given target's compilation entirely — rejected
Confirmed directly against Gleam's own gleam.toml reference documentation: no such option exists. The `target` key only sets the CLI's default target, not which files get analyzed for which target.
Accept the warning count as an untracked, unenforced fact of life (the status quo this feedback was about) — rejected
Exactly what the user flagged as unacceptable -- an unenforced 'we know about these' is indistinguishable from not knowing about them the next time a real new warning appears alongside the expected ones.
Build a checked-in baseline (tools/expected_warnings.json) and a diff-checking script (tools/check_warnings.ts) that fails on any warning not already in the reviewed baseline, or any baseline warning that's silently disappeared — chosen
Makes 'zero unexpected warnings' an enforceable, automatable fact rather than a promise that can quietly be ignored again. Confirmed gleam-lang/gleam#3808 (the upstream issue describing exactly this class of false-positive) is closed 'not planned' by the Gleam maintainers -- there is no compiler-level fix coming, so a project-level gate is the correct, permanent answer, not a workaround to revisit later.

Decision

Removed the one real dead import (gleam/float from pe_test.gleam). Added tools/check_warnings.ts (Deno/TypeScript, matching tools/browser_check.ts's own type-safety convention): does a clean rebuild, runs gleam test on both targets, parses every warning block into a (target, kind, file, source-snippet) tuple deliberately excluding line/column numbers (which drift on unrelated edits and would make the baseline spuriously stale), and diffs the resulting set against tools/expected_warnings.json (49 entries, checked in). Fails loudly -- printing exactly which warnings are new or which expected ones vanished -- unless run with --update, which is meant to be used only after a human/agent has actually reviewed and understood the change. Documented the full research trail (the upstream issue, the two workarounds tried and why neither worked, the gleam.toml check) in a new 'Compiler warnings' section of ARCHITECTURE.md, and added a project-level CLAUDE.md (this repo didn't have one) making running this check, on both gleam test targets, and (when relevant) tools/browser_check.ts an explicit, non-optional precondition for calling any task finished.

Verification

162 Erlang / 185 JavaScript gleam test cases passing on both targets (one fewer warning-causing import, zero test behavior change). tools/check_warnings.ts's own correctness was verified empirically, not assumed: deliberately introduced a real new unused import (gleam/string in pe/tier.gleam) and confirmed the check failed with exit code 1 and a clear description of exactly what was new on both targets; reverted the change and confirmed the check passed cleanly again (49/49 matching the baseline, exit code 0). deno check tools/check_warnings.ts passes with no type errors.

Consequences

Any future warning -- a real one or a new instance of the known Gleam target-analysis limitation -- now fails an explicit, describable check instead of blending into an already-large, already-ignored pile. The baseline (tools/expected_warnings.json) is a real, reviewable artifact: a PR/diff that changes it is asking a reviewer (human or agent) to actually look at what changed and why, which is the actual goal this whole investigation was in service of. If Gleam ever does add a way to scope a file to one target (there's no indication the maintainers plan to, per the closed issue), the baseline would shrink to near-zero at that point -- worth revisiting then, not a reason to wait now.

References