Summary
The HTTP server's SMS and delivery-report listing endpoints are backed by process-global lists instead of the existing SMS storage layer. SMSService can persist received SMS through SMSStore, but GET /sms/messages returns only server.py's in-memory received_messages list populated by the current process's callback.
That makes the HTTP API lose visible SMS/report history across process restarts and allows unbounded list growth during long-running Raspberry Pi deployments.
Evidence
Affected code:
server.py:22-25 declares process-global webhook_urls, received_messages, and delivery_reports lists.
server.py:119-123 returns those lists directly from GET /sms/messages and GET /sms/delivery-reports.
server.py:171-187 appends new SMS/report events to the globals.
callstack/sms/service.py:169-170 and callstack/sms/service.py:210-212 already save sent/received SMS through SMSStore, but the HTTP listing endpoint does not read from that store.
- Existing tests cover
SMSStore and SMSService, but there are no endpoint tests proving restart-safe HTTP listing behavior.
Scout reproduction/static probe on main (1c883c59575676fe6ce28214f27aeded692769b8):
PYTHONPATH=. uv run --no-project --with aiohttp --with pyserial-asyncio python - <<'PY'
from server import create_app, received_messages, delivery_reports
class FakeSMS:
async def list_messages(self):
raise RuntimeError('modem SMS store should have been queried')
async def send(self, to, body):
raise RuntimeError('not used')
class FakeUSSD:
async def send(self, code, timeout=15.0):
raise RuntimeError('not used')
class FakeModem:
sms = FakeSMS()
ussd = FakeUSSD()
app = create_app(FakeModem(), api_keys=['test-key'])
print('server globals initially:', {'received_messages': received_messages, 'delivery_reports': delivery_reports})
print('registered routes:', sorted(str(route.resource) for route in app.router.routes()))
print('GET /sms/messages handler source: returns in-memory received_messages without querying modem.sms.list_messages')
PY
Actual output:
server globals initially: {'received_messages': [], 'delivery_reports': []}
registered routes: ['<PlainResource /sms/delivery-reports>', '<PlainResource /sms/delivery-reports>', '<PlainResource /sms/messages>', '<PlainResource /sms/messages>', '<PlainResource /sms/send>', '<PlainResource /sms/subscribe>', '<PlainResource /ussd/send>']
GET /sms/messages handler source: returns in-memory received_messages without querying modem.sms.list_messages
Repository health checks from this scout run:
git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
# 307 passed in 4.24s
Duplicate searches run before filing:
gh issue list --state all --search "HTTP sms messages in-memory restart SMSStore"
gh issue list --state all --search "delivery reports in-memory restart"
gh pr list --state all --search "HTTP sms messages in-memory restart SMSStore"
gh pr list --state all --search "delivery reports in-memory restart"
# no results
Expected behavior
The HTTP API should expose durable message/report history suitable for unattended Raspberry Pi use:
- Received SMS listed after process restart if a persistent
SMSStore is configured.
- Sent SMS and delivery-report state can be correlated with the persisted sent message/reference when possible.
- Lists are bounded/paginated rather than growing forever in memory.
Actual behavior
GET /sms/messages and GET /sms/delivery-reports return process-local globals. Restarting the server resets them to empty, and a long-running process appends indefinitely.
Suggested fix direction
- Add an HTTP-facing service method or store query for received/sent messages instead of returning
received_messages.
- Persist delivery reports or update persisted sent-message status when a delivery report arrives.
- Add pagination/limits for list endpoints.
- Keep in-memory test stores available, but make the server entry point use an explicit SQLite-backed store path/config.
Acceptance criteria
GET /sms/messages can return messages persisted before an HTTP server restart when SQLite storage is configured.
- Delivery reports are persisted or reflected in stored sent-message status.
- List endpoints support a safe default limit and optional pagination/filtering.
- Endpoint tests cover restart/recreate-app behavior and avoid unbounded global-list dependence.
Verification gates
git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
Summary
The HTTP server's SMS and delivery-report listing endpoints are backed by process-global lists instead of the existing SMS storage layer.
SMSServicecan persist received SMS throughSMSStore, butGET /sms/messagesreturns onlyserver.py's in-memoryreceived_messageslist populated by the current process's callback.That makes the HTTP API lose visible SMS/report history across process restarts and allows unbounded list growth during long-running Raspberry Pi deployments.
Evidence
Affected code:
server.py:22-25declares process-globalwebhook_urls,received_messages, anddelivery_reportslists.server.py:119-123returns those lists directly fromGET /sms/messagesandGET /sms/delivery-reports.server.py:171-187appends new SMS/report events to the globals.callstack/sms/service.py:169-170andcallstack/sms/service.py:210-212already save sent/received SMS throughSMSStore, but the HTTP listing endpoint does not read from that store.SMSStoreandSMSService, but there are no endpoint tests proving restart-safe HTTP listing behavior.Scout reproduction/static probe on
main(1c883c59575676fe6ce28214f27aeded692769b8):Actual output:
Repository health checks from this scout run:
git diff --check PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q # 307 passed in 4.24sDuplicate searches run before filing:
Expected behavior
The HTTP API should expose durable message/report history suitable for unattended Raspberry Pi use:
SMSStoreis configured.Actual behavior
GET /sms/messagesandGET /sms/delivery-reportsreturn process-local globals. Restarting the server resets them to empty, and a long-running process appends indefinitely.Suggested fix direction
received_messages.Acceptance criteria
GET /sms/messagescan return messages persisted before an HTTP server restart when SQLite storage is configured.Verification gates