diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 74405ac..f53ae69 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,3 +20,12 @@ jobs:
 
       - name: Build image and run tests
         run: docker/run-tests.sh
+
+  lint:
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    steps:
+      - uses: actions/checkout@v5
+
+      - name: Ruff
+        run: pipx run ruff==0.17.0 check --output-format=github .
diff --git a/Post/createStatistics.py b/Post/createStatistics.py
index 09d098e..6ecab0a 100644
--- a/Post/createStatistics.py
+++ b/Post/createStatistics.py
@@ -8,11 +8,9 @@
 #
 # Licensed under the MIT license, see COPYING.MIT for details
 
-import sys, os
 from Post.models import Build, BuildFailure
 from django.db.models import Count
-import re
-from datetime import datetime, timedelta
+from datetime import timedelta
 from django import utils
 
 class Statistics:
@@ -52,7 +50,7 @@ class Statistics:
         elif string == "NATIVELSBSTRING":
             errors = Build.objects.filter(DATE__range=[enddate, startdate]).values('NATIVELSBSTRING').annotate(dcount=Count('NATIVELSBSTRING')).order_by('-dcount')[:6]
             items = list(errors)
-            return items;
+            return items
         elif string == "TARGET_SYS":
             errors = Build.objects.filter(DATE__range=[enddate, startdate]).values('TARGET_SYS').annotate(dcount=Count('TARGET_SYS')).order_by('-dcount')[:6]
             items = list(errors)
diff --git a/Post/models.py b/Post/models.py
index 541f394..eef0c0c 100644
--- a/Post/models.py
+++ b/Post/models.py
@@ -7,9 +7,7 @@
 # Licensed under the MIT license, see COPYING.MIT for details
 
 from django.db import models
-from django.db.models.signals import post_save
 from django.conf import settings
-from datetime import datetime
 
 import Levenshtein
 
@@ -117,7 +115,7 @@ class BuildFailure(models.Model):
 
         recalc_lev_distance = kwargs.pop('recalc_lev_distance', False)
 
-        if self.LEV_DISTANCE == None or recalc_lev_distance:
+        if self.LEV_DISTANCE is None or recalc_lev_distance:
             self.LEV_DISTANCE = self.calc_lev_distance()
 
         super(BuildFailure, self).save(*args, **kwargs)
diff --git a/Post/parser.py b/Post/parser.py
index b6740d0..092ce24 100644
--- a/Post/parser.py
+++ b/Post/parser.py
@@ -8,7 +8,8 @@
 #
 # Licensed under the MIT license, see COPYING.MIT for details
 
-import json, re
+import json
+import re
 import bleach
 from Post.models import Build, BuildFailure, ErrorType
 from Post.validators import clean_link_back
@@ -26,7 +27,7 @@ class Parser:
 
         try:
             jsondata = json.loads(self.data)
-        except:
+        except Exception:
              return  { 'error' : 'Invalid json' }
 
         # Bleach data going directly into the database so that
diff --git a/Post/purge.py b/Post/purge.py
index f4989fb..eb2ee7b 100644
--- a/Post/purge.py
+++ b/Post/purge.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta
+from datetime import timedelta
 from django.utils import timezone
 import os
 import sys
diff --git a/Post/templatetags/projecttags.py b/Post/templatetags/projecttags.py
index a01989f..838b8f7 100644
--- a/Post/templatetags/projecttags.py
+++ b/Post/templatetags/projecttags.py
@@ -1,8 +1,4 @@
-from datetime import datetime, timedelta
-import re
 from django import template
-from django.utils import timezone
-from django.template.defaultfilters import filesizeformat
 from Post.validators import clean_link_back
 
 register = template.Library()
diff --git a/Post/test.py b/Post/test.py
index 9527a9f..2a98bfc 100755
--- a/Post/test.py
+++ b/Post/test.py
@@ -1,4 +1,6 @@
-import urllib.request, urllib.parse, urllib.error
+import urllib.request
+import urllib.parse
+import urllib.error
 import json
 from unittest.mock import patch
 from django.core.management import call_command
@@ -389,6 +391,15 @@ class LinkBackSubmissionTest(TestCase):
 
 
 
+@override_settings(ALLOWED_HOSTS=["testhost"])
+class PaginationTest(TestCase):
+    def test_invalid_page_shows_last_page(self):
+        client = Client(HTTP_HOST="testhost")
+        for page in ("999", "abc"):
+            response = client.get("/Errors/Latest/", {"page": page})
+            self.assertEqual(response.status_code, 200)
+
+
 @patch("Post.management.commands.culldb.time.sleep")
 class CullDbTest(TestCase):
     def setUp(self):
diff --git a/Post/views.py b/Post/views.py
index add68d5..9b6656d 100644
--- a/Post/views.py
+++ b/Post/views.py
@@ -10,19 +10,17 @@
 # vi: tabstop=8 expandtab shiftwidth=4 softtabstop=4
 
 from __future__ import division
-from django.shortcuts import get_object_or_404
 from django.shortcuts import HttpResponse, render
 from django.views.decorators.csrf import csrf_exempt
 from django.shortcuts import redirect
-from Post.models import BuildFailure, Build, ErrorType
+from Post.models import BuildFailure, ErrorType
 from Post.parser import Parser
 from django.conf import settings
 from Post.createStatistics import Statistics
-from django.core.paginator import Paginator, EmptyPage
+from django.core.paginator import InvalidPage, Paginator
 from django.core.exceptions import FieldError, ObjectDoesNotExist, RequestDataTooBig
 from django.http import JsonResponse
 from django.db.models import Q
-import json
 import re
 import urllib
 from urllib.parse import urlparse
@@ -76,7 +74,7 @@ def addData(request, return_json=False):
         if return_json:
             response = JsonResponse(result)
         else:
-            if not 'error' in result:
+            if 'error' not in result:
               response = HttpResponse("Your entry can be found here: "+result['build_url'])
             else:
               response = HttpResponse(result['error'])
@@ -252,7 +250,7 @@ def search(request, mode=results_mode.LATEST, **kwargs):
 
     try:
         build_failures = build_failures.page(page)
-    except:
+    except InvalidPage:
         build_failures = build_failures.page(build_failures.num_pages)
 
     context['build_failures'] = build_failures
diff --git a/ruff.toml b/ruff.toml
new file mode 100644
index 0000000..cd9662d
--- /dev/null
+++ b/ruff.toml
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: MIT
+
+[lint]
+# Ruff's classic defaults: bugs, not style.
+select = ["E4", "E7", "E9", "F"]
diff --git a/test-data/test-send-error.py b/test-data/test-send-error.py
index de2fc49..7a8067f 100755
--- a/test-data/test-send-error.py
+++ b/test-data/test-send-error.py
@@ -3,7 +3,9 @@
 
 # test/example script for sending data to error-report-web
 
-import urllib.request, urllib.error, urllib.parse
+import urllib.request
+import urllib.error
+import urllib.parse
 import sys
 
 def send_data (url, data_file):
