diff --git a/firmware/main/cloud_client.cpp b/firmware/main/cloud_client.cpp
index e9cec79..6513875 100644
--- a/firmware/main/cloud_client.cpp
+++ b/firmware/main/cloud_client.cpp
@@ -141,8 +141,8 @@ static void send_hello(void)
     // playback. Playback stays FROZEN: no speech_playback capability is
     // advertised, so the Gateway never sends speech_start/chunk/end to this
     // firmware and a saved mode cannot re-enable playback.
-    const char *capabilities[]={"agent_target","approval","voice_input","settings","catalog","target-select","permissions","native-interaction","session-create","speech-settings"};
-    cJSON_AddItemToObject(root,"capabilities",cJSON_CreateStringArray(capabilities,10));
+    const char *capabilities[]={"agent_target","approval","voice_input","settings","catalog","target-select","permissions","native-interaction","session-create","speech-settings","history"};
+    cJSON_AddItemToObject(root,"capabilities",cJSON_CreateStringArray(capabilities,sizeof(capabilities)/sizeof(capabilities[0])));
     if (send_json(root)) {
         ESP_LOGI(TAG, "[CLOUD] sent hello device_id=%s", s_device_id);
     }
diff --git a/scripts/test-wearable-shell.sh b/scripts/test-wearable-shell.sh
index b5f0331..39e72fa 100755
--- a/scripts/test-wearable-shell.sh
+++ b/scripts/test-wearable-shell.sh
@@ -41,3 +41,5 @@ c++ -std=c++17 -Wall -Wextra -Werror -I "$repo_dir/boards/common/include" \
 c++ -std=c++17 -Wall -Wextra -Werror -I "$repo_dir/boards/common/include" \
     "$repo_dir/firmware/tests/agent_history_test.cpp" -o "$test_bin"
 "$test_bin"
+
+python3 "$repo_dir/firmware/tests/run_cloud_hello_test.py"
--- /dev/null
+++ b/firmware/tests/run_cloud_hello_test.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+"""Compile production send_hello and verify the actual negotiated wire contract."""
+import os
+from pathlib import Path
+import re
+import subprocess
+import tempfile
+
+repo = Path(__file__).resolve().parents[2]
+source = (repo / "firmware/main/cloud_client.cpp").read_text()
+hello = re.search(r"^static void send_hello\(void\)\s*\{.*?^}", source, re.M | re.S).group()
+cjson = Path(os.environ.get("CJSON_DIR", repo / ".cache/esp-idf-v5.5.4/components/json/cJSON"))
+harness = r'''#include "cJSON.h"
+#include <cassert>
+#include <cstring>
+#include <cstdio>
+#define CONFIG_DEVICE_LAB_DEVICE_TOKEN "test-only-token"
+#define DEVICE_LAB_FIRMWARE_VERSION "test-version"
+#define ESP_LOGI(...) ((void)0)
+static const char* s_device_id = "test-device";
+static const char* TAG = "test";
+static const char* board_slug() { return "m5stack-stopwatch"; }
+static cJSON* captured = nullptr;
+static bool send_json(cJSON* root) { captured = root; return true; }
+'''
+harness += hello
+harness += r'''
+int main() {
+    (void)TAG;
+    send_hello();
+    assert(captured);
+    assert(std::strcmp(cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(captured, "type")), "hello") == 0);
+    auto* capabilities = cJSON_GetObjectItemCaseSensitive(captured, "capabilities");
+    assert(cJSON_IsArray(capabilities));
+    // These capabilities unlock implemented read-only History and its button entry.
+    const char* required[] = {"history", "catalog", "target-select", "native-interaction", "speech-settings"};
+    for (auto* expected : required) {
+        int matches = 0;
+        cJSON* item = nullptr;
+        cJSON_ArrayForEach(item, capabilities) {
+            assert(cJSON_IsString(item));
+            if (std::strcmp(item->valuestring, expected) == 0) ++matches;
+            assert(std::strcmp(item->valuestring, "speech_playback") != 0);
+        }
+        assert(matches == 1);
+    }
+    cJSON_Delete(captured);
+    std::puts("production hello advertises History and preserves playback freeze PASS");
+}
+'''
+with tempfile.TemporaryDirectory(prefix="cloud-hello-") as folder:
+    temp = Path(folder)
+    (temp / "hello.cpp").write_text(harness)
+    subprocess.run(["cc", "-c", str(cjson / "cJSON.c"), "-I", str(cjson), "-o", str(temp / "cjson.o")], check=True)
+    subprocess.run(["c++", "-std=c++17", "-Wall", "-Wextra", "-Werror", "-I", str(cjson), str(temp / "hello.cpp"), str(temp / "cjson.o"), "-o", str(temp / "test")], check=True)
+    subprocess.run([str(temp / "test")], check=True)
