Summary
gaze crap and gaze report use different threshold models for
--max-gaze-crapload, producing different exit codes for the same
unavailable-data condition:
gaze report (EvaluateThresholds): *int thresholds +
cmd.Flags().Changed(). When the threshold is set but GazeCRAPload is
nil, it fails with "GazeCRAPload (unavailable)".
gaze crap (checkCIThresholds): plain int thresholds with
0 = disabled. When GazeCRAPload is nil, it silently skips the check.
Reproduction
# gaze report: threshold set, GazeCRAP unavailable - FAIL
$ gaze report --format=json --max-gaze-crapload=5 . 2>&1 | grep GazeCRAPload
GazeCRAPload (unavailable): N/A/5 (FAIL)
# gaze crap: threshold set, GazeCRAP unavailable - silently skipped
$ gaze crap --max-gaze-crapload=5 . 2>&1 | grep -i gaze
note: GazeCRAP unavailable - run 'gaze quality' to compute contract coverage
$ echo $?
0
Expected vs Actual
- Expected: Both commands should produce the same pass/fail result for the
same threshold flag and the same unavailable-data condition.
- Actual:
gaze report fails the gate; gaze crap silently skips it.
Root cause
checkCIThresholds in cmd/gaze/main.go uses plain int flags with
0 = no limit semantics. The nil-check at line 689 skips the comparison
when GazeCRAPload is nil, but does not fail the gate:
if maxGazeCrapload > 0 && rpt.Summary.GazeCRAPload != nil &&
*rpt.Summary.GazeCRAPload > maxGazeCrapload {
This also means --max-crapload=0 is treated as "disabled" rather than
"fail if any function exceeds 0" - a second divergence from
gaze report's *int model where zero is a live threshold.
Origin
The ci-gate-integrity design document (PR #148,
openspec/changes/ci-gate-integrity/design.md) lists alignment as a
goal but explicitly defers the refactoring as a non-goal:
Aligning gaze crap's int-based threshold model with gaze report's
*int + cmd.Flags().Changed() model - that's a separate refactoring
task.
The code comment at cmd/gaze/main.go:686-691 also documents the
divergence.
Suggested fix
Refactor gaze crap threshold handling to use *int flags and
cmd.Flags().Changed() for all threshold checks (not just the zero-result
gate added in #148). Unify checkCIThresholds with EvaluateThresholds
or replace it entirely so both commands share the same threshold semantics.
cmd/gaze/main.go-checkCIThresholds(lines 686-691)gaze crapreport --max-gaze-craploadalways PASSES when GazeCRAP is unavailable #108, PR fix: prevent silent CI passes on missing/partial data (#101, #108, #116) #148Summary
gaze crapandgaze reportuse different threshold models for--max-gaze-crapload, producing different exit codes for the sameunavailable-data condition:
gaze report(EvaluateThresholds):*intthresholds +cmd.Flags().Changed(). When the threshold is set butGazeCRAPloadisnil, it fails with "GazeCRAPload (unavailable)".gaze crap(checkCIThresholds): plainintthresholds with0 = disabled. WhenGazeCRAPloadisnil, it silently skips the check.Reproduction
Expected vs Actual
same threshold flag and the same unavailable-data condition.
gaze reportfails the gate;gaze crapsilently skips it.Root cause
checkCIThresholdsincmd/gaze/main.gouses plainintflags with0 = no limitsemantics. The nil-check at line 689 skips the comparisonwhen
GazeCRAPloadisnil, but does not fail the gate:This also means
--max-crapload=0is treated as "disabled" rather than"fail if any function exceeds 0" - a second divergence from
gaze report's*intmodel where zero is a live threshold.Origin
The ci-gate-integrity design document (PR #148,
openspec/changes/ci-gate-integrity/design.md) lists alignment as agoal but explicitly defers the refactoring as a non-goal:
The code comment at
cmd/gaze/main.go:686-691also documents thedivergence.
Suggested fix
Refactor
gaze crapthreshold handling to use*intflags andcmd.Flags().Changed()for all threshold checks (not just the zero-resultgate added in #148). Unify
checkCIThresholdswithEvaluateThresholdsor replace it entirely so both commands share the same threshold semantics.