Skip to content

[BUG] suppress_repeated_errors never honored — 'error,repeated' case shadowed by startswith('error') guard in job_is_reportable() #189

Description

@m3nu

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.

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