dgtlmoon/changedetection.io · error

abort(500)

Error message

abort(500)

What it means

A catch-all abort(500) at the end of the 'get latest rendered HTML' endpoint. The happy path decompresses the latest snapshot (e.g. zlib/gzip) and streams it; if fetching/decompressing the snapshot produced nothing usable, execution falls through to the 500. It signals a corrupt or unreadable latest snapshot rather than a missing one (missing watches 404 earlier).

Source

Thrown at changedetectionio/blueprint/ui/edit.py:398

        if uuid == 'first':
            uuid = list(datastore.data['watching'].keys()).pop()
        watch = datastore.data['watching'].get(uuid)
        if watch and watch.history.keys() and os.path.isdir(watch.data_dir):
            latest_filename = list(watch.history.keys())[-1]
            html_fname = os.path.join(watch.data_dir, f"{latest_filename}.html.br")
            with open(html_fname, 'rb') as f:
                if html_fname.endswith('.br'):
                    # Read and decompress the Brotli file
                    decompressed_data = brotli.decompress(f.read())
                else:
                    decompressed_data = f.read()

            buffer = BytesIO(decompressed_data)

            return send_file(buffer, as_attachment=True, download_name=f"{latest_filename}.html", mimetype='text/html')

        # Return a 500 error
        abort(500)

    @edit_blueprint.route("/edit/<uuid_str:uuid>/get-data-package", methods=['GET'])
    @login_optionally_required
    def watch_get_data_package(uuid):
        """Download all data for a single watch as a zip file"""
        from io import BytesIO
        from flask import send_file
        import zipfile
        from pathlib import Path
        import datetime

        watch = datastore.data['watching'].get(uuid)
        if not watch:
            abort(404)

        # Create zip in memory
        memory_file = BytesIO()

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Run a recheck for the watch so a fresh snapshot replaces the corrupt latest one
  2. Inspect the watch's data_dir snapshot files and delete the corrupt latest snapshot
  3. Restore the datastore from backup if multiple watches are affected
  4. If it persists, report with the watch's snapshot file for compression-format investigation

Example fix

# before
resp = requests.get(f'{base}/edit/{uuid}/get-html')
assert resp.status_code == 200

# after
resp = requests.get(f'{base}/edit/{uuid}/get-html')
if resp.status_code == 500:
    requests.get(f'{base}/checknow', params={'uuid': uuid})  # regenerate snapshot
    resp = requests.get(f'{base}/edit/{uuid}/get-html')
resp.raise_for_status()
Defensive patterns

Strategy: retry

Validate before calling

# no client-side precheck exists; verify snapshot health indirectly
import requests
def snapshot_ok(base, uuid):
    return requests.get(f'{base}/edit/{uuid}/get-html').status_code == 200

Try / catch

try:
    r = requests.get(f'{base}/edit/{uuid}/get-html')
    r.raise_for_status()
except requests.HTTPError as e:
    if e.response.status_code == 500:
        requests.get(f'{base}/checknow', params={'uuid': uuid})
        r = requests.get(f'{base}/edit/{uuid}/get-html')
        r.raise_for_status()
    else:
        raise

Prevention

When it happens

Trigger: GET /edit/<uuid>/get-html when the stored latest snapshot is empty or fails decompression (truncated write, wrong compression headers, disk corruption), or the watch has a history entry whose payload cannot be decompressed.

Common situations: Datastore corruption after a crash mid-write; snapshots written by an older version using a different compression; disk-full events truncating snapshot files; empty history from a failed first check.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/9f1f2f0dbac8f679. Report an issue: GitHub.