Describe the bug
suppress_repeated_errors (and the built-in dedup of repeated identical errors) is never honored. A job that keeps failing with the same error message produces an error report on every run, even with suppress_repeated_errors: true.
Root cause
In webchanges/handler.py, Report.get_filtered_job_states() → job_is_reportable() gates notifications with a match job_state.verb block. The guard for the generic error* verbs is ordered before the dedicated 'error,repeated' case:
match job_state.verb:
...
case e if e.startswith('error'): # matches 'error' AND 'error,repeated'
if job_state.job.suppress_errors is not None:
return not job_state.job.suppress_errors
return display_cfg['error']
case 'error,repeated': # UNREACHABLE
if job_state.job.suppress_repeated_errors is not None:
return job_state.job.suppress_repeated_errors
return display_cfg['error']
...
Python's match takes the first matching case. 'error,repeated'.startswith('error') is True, so verb error,repeated is always caught by the startswith('error') guard and the dedicated case 'error,repeated' is dead code. suppress_repeated_errors is therefore never consulted; repeated errors fall through to display_cfg['error'] (default true) and are reported every run.
Everything upstream of the gate is correct: worker.py sets the verb error,repeated when a job fails with the same message as before, so the verb reaches the gate — it is just never matched by the intended case.
Regression window
Worked in v3.33.0; broken from v3.34.0 onward (the job_is_reportable refactor introduced by #101). Still present on main (verified: handler.py lines ~457–464).
Fix
Move case 'error,repeated' above the case e if e.startswith('error') guard, or narrow the guard to case 'error', so the more specific verb is matched first. For example:
case 'error,repeated':
if job_state.job.suppress_repeated_errors is not None:
return job_state.job.suppress_repeated_errors
return display_cfg['error']
case e if e.startswith('error'):
if job_state.job.suppress_errors is not None:
return not job_state.job.suppress_errors
return display_cfg['error']
Expected vs actual
- Expected: with
suppress_repeated_errors: true, a persistently-failing job reports the error once, then stays silent until the error changes or resolves.
- Actual: an error report is emitted on every run.
Describe the bug
suppress_repeated_errors(and the built-in dedup of repeated identical errors) is never honored. A job that keeps failing with the same error message produces an error report on every run, even withsuppress_repeated_errors: true.Root cause
In
webchanges/handler.py,Report.get_filtered_job_states()→job_is_reportable()gates notifications with amatch job_state.verbblock. The guard for the genericerror*verbs is ordered before the dedicated'error,repeated'case:Python's
matchtakes the first matching case.'error,repeated'.startswith('error')isTrue, so verberror,repeatedis always caught by thestartswith('error')guard and the dedicatedcase 'error,repeated'is dead code.suppress_repeated_errorsis therefore never consulted; repeated errors fall through todisplay_cfg['error'](defaulttrue) and are reported every run.Everything upstream of the gate is correct:
worker.pysets the verberror,repeatedwhen a job fails with the same message as before, so the verb reaches the gate — it is just never matched by the intended case.Regression window
Worked in v3.33.0; broken from v3.34.0 onward (the
job_is_reportablerefactor introduced by #101). Still present onmain(verified:handler.pylines ~457–464).Fix
Move
case 'error,repeated'above thecase e if e.startswith('error')guard, or narrow the guard tocase 'error', so the more specific verb is matched first. For example:Expected vs actual
suppress_repeated_errors: true, a persistently-failing job reports the error once, then stays silent until the error changes or resolves.