We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Update first message ID when first message is deleted
When a message is deleted, we should update the stored ID of the first message in the stream. Because we currently do not, deleting the first message may result in an extraneous "more topics" link in the left sidebar, with no additional topics shown when you click it.

Note: The symptom may be hard to replicate; we should focus on fixing the technical issue, as described in @timabbott 's comment below.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/actions/message_delete.py`
Content:
```
1 from typing import Iterable, List, TypedDict
2 
3 from zerver.lib import retention
4 from zerver.lib.retention import move_messages_to_archive
5 from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
6 from zerver.models import Message, Realm, UserMessage, UserProfile
7 from zerver.tornado.django_api import send_event_on_commit
8 
9 
10 class DeleteMessagesEvent(TypedDict, total=False):
11     type: str
12     message_ids: List[int]
13     message_type: str
14     topic: str
15     stream_id: int
16 
17 
18 def do_delete_messages(realm: Realm, messages: Iterable[Message]) -> None:
19     # messages in delete_message event belong to the same topic
20     # or is a single direct message, as any other behaviour is not possible with
21     # the current callers to this method.
22     messages = list(messages)
23     message_ids = [message.id for message in messages]
24     if not message_ids:
25         return
26 
27     event: DeleteMessagesEvent = {
28         "type": "delete_message",
29         "message_ids": message_ids,
30     }
31 
32     sample_message = messages[0]
33     message_type = "stream"
34     users_to_notify = []
35     if not sample_message.is_stream_message():
36         assert len(messages) == 1
37         message_type = "private"
38         ums = UserMessage.objects.filter(message_id__in=message_ids)
39         users_to_notify = [um.user_profile_id for um in ums]
40         archiving_chunk_size = retention.MESSAGE_BATCH_SIZE
41 
42     if message_type == "stream":
43         stream_id = sample_message.recipient.type_id
44         event["stream_id"] = stream_id
45         event["topic"] = sample_message.topic_name()
46         subscriptions = get_active_subscriptions_for_stream_id(
47             stream_id, include_deactivated_users=False
48         )
49         # We exclude long-term idle users, since they by definition have no active clients.
50         subscriptions = subscriptions.exclude(user_profile__long_term_idle=True)
51         users_to_notify = list(subscriptions.values_list("user_profile_id", flat=True))
52         archiving_chunk_size = retention.STREAM_MESSAGE_BATCH_SIZE
53 
54     move_messages_to_archive(message_ids, realm=realm, chunk_size=archiving_chunk_size)
55 
56     event["message_type"] = message_type
57     send_event_on_commit(realm, event, users_to_notify)
58 
59 
60 def do_delete_messages_by_sender(user: UserProfile) -> None:
61     message_ids = list(
62         # Uses index: zerver_message_realm_sender_recipient (prefix)
63         Message.objects.filter(realm_id=user.realm_id, sender=user)
64         .values_list("id", flat=True)
65         .order_by("id")
66     )
67     if message_ids:
68         move_messages_to_archive(message_ids, chunk_size=retention.STREAM_MESSAGE_BATCH_SIZE)
69
```

Path: `version.py`
Content:
```
1 import os
2 
3 ZULIP_VERSION = "9.0-dev+git"
4 
5 # Add information on number of commits and commit hash to version, if available
6 zulip_git_version_file = os.path.join(
7     os.path.dirname(os.path.abspath(__file__)), "zulip-git-version"
8 )
9 lines = [ZULIP_VERSION, ""]
10 if os.path.exists(zulip_git_version_file):
11     with open(zulip_git_version_file) as f:
12         lines = [*f, "", ""]
13 ZULIP_VERSION = lines.pop(0).strip()
14 ZULIP_MERGE_BASE = lines.pop(0).strip()
15 
16 LATEST_MAJOR_VERSION = "8.0"
17 LATEST_RELEASE_VERSION = "8.3"
18 LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.com/2023/12/15/zulip-8-0-released/"
19 
20 # Versions of the desktop app below DESKTOP_MINIMUM_VERSION will be
21 # prevented from connecting to the Zulip server.  Versions above
22 # DESKTOP_MINIMUM_VERSION but below DESKTOP_WARNING_VERSION will have
23 # a banner at the top of the page asking the user to upgrade.
24 DESKTOP_MINIMUM_VERSION = "5.4.3"
25 DESKTOP_WARNING_VERSION = "5.9.3"
26 
27 # Bump the API_FEATURE_LEVEL whenever an API change is made
28 # that clients might want to condition on.  If we forget at
29 # the time we make the change, then bump it later as soon
30 # as we notice; clients using API_FEATURE_LEVEL will just not
31 # use the new feature/API until the bump.
32 #
33 # Changes should be accompanied by documentation explaining what the
34 # new level means in api_docs/changelog.md, as well as "**Changes**"
35 # entries in the endpoint's documentation in `zulip.yaml`.
36 API_FEATURE_LEVEL = 255
37 
38 # Bump the minor PROVISION_VERSION to indicate that folks should provision
39 # only when going from an old version of the code to a newer version. Bump
40 # the major version to indicate that folks should provision in both
41 # directions.
42 
43 # Typically,
44 # * adding a dependency only requires a minor version bump;
45 # * removing a dependency requires a major version bump;
46 # * upgrading a dependency requires a major version bump, unless the
47 #   upgraded dependency is backwards compatible with all of our
48 #   historical commits sharing the same major version, in which case a
49 #   minor version bump suffices.
50 
51 PROVISION_VERSION = (269, 3)  # last bumped 2024-04-29 for adding pyasyncore
52
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/version.py b/version.py
--- a/version.py
+++ b/version.py
@@ -33,7 +33,7 @@
 # Changes should be accompanied by documentation explaining what the
 # new level means in api_docs/changelog.md, as well as "**Changes**"
 # entries in the endpoint's documentation in `zulip.yaml`.
-API_FEATURE_LEVEL = 255
+API_FEATURE_LEVEL = 256
 
 # Bump the minor PROVISION_VERSION to indicate that folks should provision
 # only when going from an old version of the code to a newer version. Bump
diff --git a/zerver/actions/message_delete.py b/zerver/actions/message_delete.py
--- a/zerver/actions/message_delete.py
+++ b/zerver/actions/message_delete.py
@@ -3,7 +3,7 @@
 from zerver.lib import retention
 from zerver.lib.retention import move_messages_to_archive
 from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id
-from zerver.models import Message, Realm, UserMessage, UserProfile
+from zerver.models import Message, Realm, Stream, UserMessage, UserProfile
 from zerver.tornado.django_api import send_event_on_commit
 
 
@@ -15,6 +15,34 @@
     stream_id: int
 
 
+def check_update_first_message_id(
+    realm: Realm, stream: Stream, message_ids: List[int], users_to_notify: Iterable[int]
+) -> None:
+    # This will not update the `first_message_id` of streams where the
+    # first message was deleted prior to the implementation of this function.
+    assert stream.recipient_id is not None
+    if stream.first_message_id not in message_ids:
+        return
+    current_first_message_id = (
+        Message.objects.filter(realm_id=realm.id, recipient_id=stream.recipient_id)
+        .values_list("id", flat=True)
+        .order_by("id")
+        .first()
+    )
+
+    stream.first_message_id = current_first_message_id
+    stream.save(update_fields=["first_message_id"])
+
+    stream_event = dict(
+        type="stream",
+        op="update",
+        property="first_message_id",
+        value=stream.first_message_id,
+        stream_id=stream.id,
+    )
+    send_event_on_commit(realm, stream_event, users_to_notify)
+
+
 def do_delete_messages(realm: Realm, messages: Iterable[Message]) -> None:
     # messages in delete_message event belong to the same topic
     # or is a single direct message, as any other behaviour is not possible with
@@ -52,6 +80,9 @@
         archiving_chunk_size = retention.STREAM_MESSAGE_BATCH_SIZE
 
     move_messages_to_archive(message_ids, realm=realm, chunk_size=archiving_chunk_size)
+    if message_type == "stream":
+        stream = Stream.objects.get(id=sample_message.recipient.type_id)
+        check_update_first_message_id(realm, stream, message_ids, users_to_notify)
 
     event["message_type"] = message_type
     send_event_on_commit(realm, event, users_to_notify)

{"golden_diff": "diff --git a/version.py b/version.py\n--- a/version.py\n+++ b/version.py\n@@ -33,7 +33,7 @@\n # Changes should be accompanied by documentation explaining what the\n # new level means in api_docs/changelog.md, as well as \"**Changes**\"\n # entries in the endpoint's documentation in `zulip.yaml`.\n-API_FEATURE_LEVEL = 255\n+API_FEATURE_LEVEL = 256\n \n # Bump the minor PROVISION_VERSION to indicate that folks should provision\n # only when going from an old version of the code to a newer version. Bump\ndiff --git a/zerver/actions/message_delete.py b/zerver/actions/message_delete.py\n--- a/zerver/actions/message_delete.py\n+++ b/zerver/actions/message_delete.py\n@@ -3,7 +3,7 @@\n from zerver.lib import retention\n from zerver.lib.retention import move_messages_to_archive\n from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id\n-from zerver.models import Message, Realm, UserMessage, UserProfile\n+from zerver.models import Message, Realm, Stream, UserMessage, UserProfile\n from zerver.tornado.django_api import send_event_on_commit\n \n \n@@ -15,6 +15,34 @@\n     stream_id: int\n \n \n+def check_update_first_message_id(\n+    realm: Realm, stream: Stream, message_ids: List[int], users_to_notify: Iterable[int]\n+) -> None:\n+    # This will not update the `first_message_id` of streams where the\n+    # first message was deleted prior to the implementation of this function.\n+    assert stream.recipient_id is not None\n+    if stream.first_message_id not in message_ids:\n+        return\n+    current_first_message_id = (\n+        Message.objects.filter(realm_id=realm.id, recipient_id=stream.recipient_id)\n+        .values_list(\"id\", flat=True)\n+        .order_by(\"id\")\n+        .first()\n+    )\n+\n+    stream.first_message_id = current_first_message_id\n+    stream.save(update_fields=[\"first_message_id\"])\n+\n+    stream_event = dict(\n+        type=\"stream\",\n+        op=\"update\",\n+        property=\"first_message_id\",\n+        value=stream.first_message_id,\n+        stream_id=stream.id,\n+    )\n+    send_event_on_commit(realm, stream_event, users_to_notify)\n+\n+\n def do_delete_messages(realm: Realm, messages: Iterable[Message]) -> None:\n     # messages in delete_message event belong to the same topic\n     # or is a single direct message, as any other behaviour is not possible with\n@@ -52,6 +80,9 @@\n         archiving_chunk_size = retention.STREAM_MESSAGE_BATCH_SIZE\n \n     move_messages_to_archive(message_ids, realm=realm, chunk_size=archiving_chunk_size)\n+    if message_type == \"stream\":\n+        stream = Stream.objects.get(id=sample_message.recipient.type_id)\n+        check_update_first_message_id(realm, stream, message_ids, users_to_notify)\n \n     event[\"message_type\"] = message_type\n     send_event_on_commit(realm, event, users_to_notify)\n", "issue": "Update first message ID when first message is deleted\nWhen a message is deleted, we should update the stored ID of the first message in the stream. Because we currently do not, deleting the first message may result in an extraneous \"more topics\" link in the left sidebar, with no additional topics shown when you click it.\r\n\r\nNote: The symptom may be hard to replicate; we should focus on fixing the technical issue, as described in @timabbott 's comment below.\n", "before_files": [{"content": "from typing import Iterable, List, TypedDict\n\nfrom zerver.lib import retention\nfrom zerver.lib.retention import move_messages_to_archive\nfrom zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id\nfrom zerver.models import Message, Realm, UserMessage, UserProfile\nfrom zerver.tornado.django_api import send_event_on_commit\n\n\nclass DeleteMessagesEvent(TypedDict, total=False):\n    type: str\n    message_ids: List[int]\n    message_type: str\n    topic: str\n    stream_id: int\n\n\ndef do_delete_messages(realm: Realm, messages: Iterable[Message]) -> None:\n    # messages in delete_message event belong to the same topic\n    # or is a single direct message, as any other behaviour is not possible with\n    # the current callers to this method.\n    messages = list(messages)\n    message_ids = [message.id for message in messages]\n    if not message_ids:\n        return\n\n    event: DeleteMessagesEvent = {\n        \"type\": \"delete_message\",\n        \"message_ids\": message_ids,\n    }\n\n    sample_message = messages[0]\n    message_type = \"stream\"\n    users_to_notify = []\n    if not sample_message.is_stream_message():\n        assert len(messages) == 1\n        message_type = \"private\"\n        ums = UserMessage.objects.filter(message_id__in=message_ids)\n        users_to_notify = [um.user_profile_id for um in ums]\n        archiving_chunk_size = retention.MESSAGE_BATCH_SIZE\n\n    if message_type == \"stream\":\n        stream_id = sample_message.recipient.type_id\n        event[\"stream_id\"] = stream_id\n        event[\"topic\"] = sample_message.topic_name()\n        subscriptions = get_active_subscriptions_for_stream_id(\n            stream_id, include_deactivated_users=False\n        )\n        # We exclude long-term idle users, since they by definition have no active clients.\n        subscriptions = subscriptions.exclude(user_profile__long_term_idle=True)\n        users_to_notify = list(subscriptions.values_list(\"user_profile_id\", flat=True))\n        archiving_chunk_size = retention.STREAM_MESSAGE_BATCH_SIZE\n\n    move_messages_to_archive(message_ids, realm=realm, chunk_size=archiving_chunk_size)\n\n    event[\"message_type\"] = message_type\n    send_event_on_commit(realm, event, users_to_notify)\n\n\ndef do_delete_messages_by_sender(user: UserProfile) -> None:\n    message_ids = list(\n        # Uses index: zerver_message_realm_sender_recipient (prefix)\n        Message.objects.filter(realm_id=user.realm_id, sender=user)\n        .values_list(\"id\", flat=True)\n        .order_by(\"id\")\n    )\n    if message_ids:\n        move_messages_to_archive(message_ids, chunk_size=retention.STREAM_MESSAGE_BATCH_SIZE)\n", "path": "zerver/actions/message_delete.py"}, {"content": "import os\n\nZULIP_VERSION = \"9.0-dev+git\"\n\n# Add information on number of commits and commit hash to version, if available\nzulip_git_version_file = os.path.join(\n    os.path.dirname(os.path.abspath(__file__)), \"zulip-git-version\"\n)\nlines = [ZULIP_VERSION, \"\"]\nif os.path.exists(zulip_git_version_file):\n    with open(zulip_git_version_file) as f:\n        lines = [*f, \"\", \"\"]\nZULIP_VERSION = lines.pop(0).strip()\nZULIP_MERGE_BASE = lines.pop(0).strip()\n\nLATEST_MAJOR_VERSION = \"8.0\"\nLATEST_RELEASE_VERSION = \"8.3\"\nLATEST_RELEASE_ANNOUNCEMENT = \"https://blog.zulip.com/2023/12/15/zulip-8-0-released/\"\n\n# Versions of the desktop app below DESKTOP_MINIMUM_VERSION will be\n# prevented from connecting to the Zulip server.  Versions above\n# DESKTOP_MINIMUM_VERSION but below DESKTOP_WARNING_VERSION will have\n# a banner at the top of the page asking the user to upgrade.\nDESKTOP_MINIMUM_VERSION = \"5.4.3\"\nDESKTOP_WARNING_VERSION = \"5.9.3\"\n\n# Bump the API_FEATURE_LEVEL whenever an API change is made\n# that clients might want to condition on.  If we forget at\n# the time we make the change, then bump it later as soon\n# as we notice; clients using API_FEATURE_LEVEL will just not\n# use the new feature/API until the bump.\n#\n# Changes should be accompanied by documentation explaining what the\n# new level means in api_docs/changelog.md, as well as \"**Changes**\"\n# entries in the endpoint's documentation in `zulip.yaml`.\nAPI_FEATURE_LEVEL = 255\n\n# Bump the minor PROVISION_VERSION to indicate that folks should provision\n# only when going from an old version of the code to a newer version. Bump\n# the major version to indicate that folks should provision in both\n# directions.\n\n# Typically,\n# * adding a dependency only requires a minor version bump;\n# * removing a dependency requires a major version bump;\n# * upgrading a dependency requires a major version bump, unless the\n#   upgraded dependency is backwards compatible with all of our\n#   historical commits sharing the same major version, in which case a\n#   minor version bump suffices.\n\nPROVISION_VERSION = (269, 3)  # last bumped 2024-04-29 for adding pyasyncore\n", "path": "version.py"}], "after_files": [{"content": "from typing import Iterable, List, TypedDict\n\nfrom zerver.lib import retention\nfrom zerver.lib.retention import move_messages_to_archive\nfrom zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id\nfrom zerver.models import Message, Realm, Stream, UserMessage, UserProfile\nfrom zerver.tornado.django_api import send_event_on_commit\n\n\nclass DeleteMessagesEvent(TypedDict, total=False):\n    type: str\n    message_ids: List[int]\n    message_type: str\n    topic: str\n    stream_id: int\n\n\ndef check_update_first_message_id(\n    realm: Realm, stream: Stream, message_ids: List[int], users_to_notify: Iterable[int]\n) -> None:\n    # This will not update the `first_message_id` of streams where the\n    # first message was deleted prior to the implementation of this function.\n    assert stream.recipient_id is not None\n    if stream.first_message_id not in message_ids:\n        return\n    current_first_message_id = (\n        Message.objects.filter(realm_id=realm.id, recipient_id=stream.recipient_id)\n        .values_list(\"id\", flat=True)\n        .order_by(\"id\")\n        .first()\n    )\n\n    stream.first_message_id = current_first_message_id\n    stream.save(update_fields=[\"first_message_id\"])\n\n    stream_event = dict(\n        type=\"stream\",\n        op=\"update\",\n        property=\"first_message_id\",\n        value=stream.first_message_id,\n        stream_id=stream.id,\n    )\n    send_event_on_commit(realm, stream_event, users_to_notify)\n\n\ndef do_delete_messages(realm: Realm, messages: Iterable[Message]) -> None:\n    # messages in delete_message event belong to the same topic\n    # or is a single direct message, as any other behaviour is not possible with\n    # the current callers to this method.\n    messages = list(messages)\n    message_ids = [message.id for message in messages]\n    if not message_ids:\n        return\n\n    event: DeleteMessagesEvent = {\n        \"type\": \"delete_message\",\n        \"message_ids\": message_ids,\n    }\n\n    sample_message = messages[0]\n    message_type = \"stream\"\n    users_to_notify = []\n    if not sample_message.is_stream_message():\n        assert len(messages) == 1\n        message_type = \"private\"\n        ums = UserMessage.objects.filter(message_id__in=message_ids)\n        users_to_notify = [um.user_profile_id for um in ums]\n        archiving_chunk_size = retention.MESSAGE_BATCH_SIZE\n\n    if message_type == \"stream\":\n        stream_id = sample_message.recipient.type_id\n        event[\"stream_id\"] = stream_id\n        event[\"topic\"] = sample_message.topic_name()\n        subscriptions = get_active_subscriptions_for_stream_id(\n            stream_id, include_deactivated_users=False\n        )\n        # We exclude long-term idle users, since they by definition have no active clients.\n        subscriptions = subscriptions.exclude(user_profile__long_term_idle=True)\n        users_to_notify = list(subscriptions.values_list(\"user_profile_id\", flat=True))\n        archiving_chunk_size = retention.STREAM_MESSAGE_BATCH_SIZE\n\n    move_messages_to_archive(message_ids, realm=realm, chunk_size=archiving_chunk_size)\n    if message_type == \"stream\":\n        stream = Stream.objects.get(id=sample_message.recipient.type_id)\n        check_update_first_message_id(realm, stream, message_ids, users_to_notify)\n\n    event[\"message_type\"] = message_type\n    send_event_on_commit(realm, event, users_to_notify)\n\n\ndef do_delete_messages_by_sender(user: UserProfile) -> None:\n    message_ids = list(\n        # Uses index: zerver_message_realm_sender_recipient (prefix)\n        Message.objects.filter(realm_id=user.realm_id, sender=user)\n        .values_list(\"id\", flat=True)\n        .order_by(\"id\")\n    )\n    if message_ids:\n        move_messages_to_archive(message_ids, chunk_size=retention.STREAM_MESSAGE_BATCH_SIZE)\n", "path": "zerver/actions/message_delete.py"}, {"content": "import os\n\nZULIP_VERSION = \"9.0-dev+git\"\n\n# Add information on number of commits and commit hash to version, if available\nzulip_git_version_file = os.path.join(\n    os.path.dirname(os.path.abspath(__file__)), \"zulip-git-version\"\n)\nlines = [ZULIP_VERSION, \"\"]\nif os.path.exists(zulip_git_version_file):\n    with open(zulip_git_version_file) as f:\n        lines = [*f, \"\", \"\"]\nZULIP_VERSION = lines.pop(0).strip()\nZULIP_MERGE_BASE = lines.pop(0).strip()\n\nLATEST_MAJOR_VERSION = \"8.0\"\nLATEST_RELEASE_VERSION = \"8.3\"\nLATEST_RELEASE_ANNOUNCEMENT = \"https://blog.zulip.com/2023/12/15/zulip-8-0-released/\"\n\n# Versions of the desktop app below DESKTOP_MINIMUM_VERSION will be\n# prevented from connecting to the Zulip server.  Versions above\n# DESKTOP_MINIMUM_VERSION but below DESKTOP_WARNING_VERSION will have\n# a banner at the top of the page asking the user to upgrade.\nDESKTOP_MINIMUM_VERSION = \"5.4.3\"\nDESKTOP_WARNING_VERSION = \"5.9.3\"\n\n# Bump the API_FEATURE_LEVEL whenever an API change is made\n# that clients might want to condition on.  If we forget at\n# the time we make the change, then bump it later as soon\n# as we notice; clients using API_FEATURE_LEVEL will just not\n# use the new feature/API until the bump.\n#\n# Changes should be accompanied by documentation explaining what the\n# new level means in api_docs/changelog.md, as well as \"**Changes**\"\n# entries in the endpoint's documentation in `zulip.yaml`.\nAPI_FEATURE_LEVEL = 256\n\n# Bump the minor PROVISION_VERSION to indicate that folks should provision\n# only when going from an old version of the code to a newer version. Bump\n# the major version to indicate that folks should provision in both\n# directions.\n\n# Typically,\n# * adding a dependency only requires a minor version bump;\n# * removing a dependency requires a major version bump;\n# * upgrading a dependency requires a major version bump, unless the\n#   upgraded dependency is backwards compatible with all of our\n#   historical commits sharing the same major version, in which case a\n#   minor version bump suffices.\n\nPROVISION_VERSION = (269, 3)  # last bumped 2024-04-29 for adding pyasyncore\n", "path": "version.py"}]}