{"record":{"id":"a2fc82f1f0d3dcd2","repo":"dgtlmoon/changedetection.io","slug":"abort-404-a2fc82","errorCode":null,"errorMessage":"abort(404)","messagePattern":"abort\\(404\\)","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"changedetectionio/blueprint/ui/edit.py","lineNumber":412,"sourceCode":"\n            return send_file(buffer, as_attachment=True, download_name=f\"{latest_filename}.html\", mimetype='text/html')\n\n        # Return a 500 error\n        abort(500)\n\n    @edit_blueprint.route(\"/edit/<uuid_str:uuid>/get-data-package\", methods=['GET'])\n    @login_optionally_required\n    def watch_get_data_package(uuid):\n        \"\"\"Download all data for a single watch as a zip file\"\"\"\n        from io import BytesIO\n        from flask import send_file\n        import zipfile\n        from pathlib import Path\n        import datetime\n\n        watch = datastore.data['watching'].get(uuid)\n        if not watch:\n            abort(404)\n\n        # Create zip in memory\n        memory_file = BytesIO()\n\n        with zipfile.ZipFile(memory_file, 'w',\n                           compression=zipfile.ZIP_DEFLATED,\n                           compresslevel=8) as zipObj:\n\n            # Add the watch's JSON file if it exists\n            watch_json_path = os.path.join(watch.data_dir, 'watch.json')\n            if os.path.isfile(watch_json_path):\n                zipObj.write(watch_json_path,\n                           arcname=os.path.join(uuid, 'watch.json'),\n                           compress_type=zipfile.ZIP_DEFLATED,\n                           compresslevel=8)\n\n            # Add all files in the watch data directory\n            if os.path.isdir(watch.data_dir):","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/blueprint/ui/edit.py#L394-L430","documentation":"Straightforward abort(404) when building the per-watch data package zip: the uuid is not present in datastore.data['watching']. The watch lookup happens before any zip work, so this only means 'no such watch'.","triggerScenarios":"GET /edit/<uuid>/get-data-package with a deleted watch uuid, a typo'd uuid, or a uuid from a different datastore instance.","commonSituations":"Stale UI tabs/bookmarks pointing at deleted watches; scripts holding cached uuids after watches were removed and re-imported; uuid copied with extra characters or wrong case.","solutions":["List current watches (GET /api/watch) and use a valid uuid","Refresh the UI tab — it may reference a deleted watch","Confirm you are talking to the correct instance/datastore if watches were imported elsewhere"],"exampleFix":"# before\nrequests.get(f'{base}/edit/{bad_uuid}/get-data-package').raise_for_status()\n\n# after\nwatch_uuids = requests.get(f'{base}/api/watch').json().keys()\nassert uuid in watch_uuids, 'watch no longer exists'\nrequests.get(f'{base}/edit/{uuid}/get-data-package').raise_for_status()","handlingStrategy":"validation","validationCode":"import requests\ndef watch_exists(base, uuid, auth=None):\n    return uuid in requests.get(f'{base}/api/watch', auth=auth).json()","typeGuard":null,"tryCatchPattern":"try:\n    r = requests.get(f'{base}/edit/{uuid}/get-data-package')\n    r.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response.status_code == 404:\n        raise KeyError(f'watch {uuid} not found') from e\n    raise","preventionTips":["Validate uuid against the watch list before API calls","Purge cached uuids when watches are deleted/reimported","Copy uuids exactly — they are case-sensitive"],"tags":["flask","http-404","uuid","changedetectionio"],"backgroundTag":"http-404-not-found","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}