Last reviewed: 2026-09-14
This document tracks identified failure modes, architectural edge cases, and regression traps to prevent during development.
- Symptom: SMS text or OTP verification codes appear in server stdout, Android logcat, or error traces.
- Root Cause: Unmasked debug prints or logging entire request/response payloads in middleware.
- Prevention: Enforce payload masking in log interceptors. Standard and debug logs must output only message ID, sender identity, and action status. Add automated tests asserting that dummy OTP patterns do not exist in log outputs.
- Symptom: SMS arrives while phone screen is off/locked, but is not forwarded until the user wakes the screen.
- Root Cause: Standard WorkManager jobs being deferred during deep Doze / App Standby states.
- Prevention: Use high-priority foreground execution or appropriate expedited WorkManager constraints. Request
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSwhere user-approved.
- Symptom: When network latency delays server ACKs, Android retries and creates multiple duplicate records.
- Root Cause: Server insert query generating fresh auto-increment IDs without enforcing unique
message_idconstraint. - Prevention: Server schema must enforce
UNIQUE(device_id, message_id). ThePOST /api/v1/messageshandler must execute anON CONFLICT DO NOTHINGor return HTTP 200 with existing record details.
- Symptom: Server CPU spikes to 100% when an agent waits for an SMS.
- Root Cause: Implementing wait logic via a
for { db.Query(...) ; time.Sleep(...) }loop. - Prevention: Implement a pub-sub subscription using Go channels and
sync.RWMutex. The HTTP ingestion handler broadcasts new messages to active channels; the wait handler selects on the channel ortime.After(timeout).
- Symptom: Expecting Accessibility Service to unlock a PIN-locked phone or operate when phone is turned off.
- Root Cause: Misunderstanding Android OS security and hardware boundaries.
- Prevention: Clearly document physical constraints in documentation: screen off is supported, but locked keystore restrictions apply. Power-off execution is physically impossible.
- Symptom: Ingesting messages containing newlines (
\r\n) through--adb-porthook causes unexpected emulator console command execution or truncated SMS bodies. - Root Cause:
adb emu sms send <sender> <body>transmits commands over the emulator's raw telnet console, where unescaped CR/LF characters trigger new commands. - Prevention: Enforce
SanitizeADBInputon all arguments before passing toadb emu, stripping\rand translating\nto spaces. Verify via automated unit tests inhook_test.go.
- Symptom: Running
adb backupon an unlocked device extracts outbox SMS history, OTPs, and device Bearer tokens. - Root Cause: Default Android manifest configuration allows full backup unless explicitly disabled.
- Prevention: Keep
android:allowBackup="false"set inAndroidManifest.xmland maintain explicit exclusion rules inbackup_rules.xmlanddata_extraction_rules.xml.
- Symptom: Android Studio / Compose compiler lint warning: "Modifier parameter should be the first optional parameter".
- Root Cause: Placing optional ViewModel defaults (e.g.
viewModel: MessageListViewModel = koinViewModel()) or optional event callbacks ahead ofmodifier: Modifier = Modifier. - Prevention: Strictly order Composable parameters: (1) required parameters without default values, (2)
modifier: Modifier = Modifieras the first optional parameter, (3) remaining optional parameters with default values.
- Symptom: Server-Sent Events (SSE) endpoints return
streaming unsupported by server(HTTP 500) when accessed through standard HTTP middleware chains. - Root Cause: Interceptor structs that wrap
http.ResponseWriter(e.g. to log response status codes) embed the interface but do not explicitly implementhttp.FlusherorUnwrap() http.ResponseWriter. The standard type assertionw.(http.Flusher)fails on the wrapped struct. - Prevention: Any custom
http.ResponseWriterwrapper must delegateFlush()to the underlying writer if it implementshttp.Flusher, and provideUnwrap() http.ResponseWriter(Go 1.20+ convention). Verify SSE streaming in end-to-end integration tests through the full middleware stack.
- Symptom: Deleting a device via
DELETE /api/v1/dashboard/devices/{id}fails withFOREIGN KEY constraint failed(HTTP 500) if the device has previously ingested messages. - Root Cause: In SQLite with foreign keys active (
PRAGMA foreign_keys = ON;), themessagestable schema definesdevice_id TEXT NOT NULL REFERENCES devices(id)withoutON DELETE CASCADE. ExecutingDELETE FROM devices WHERE id = ?fails immediately. - Prevention: Implement repository deletion inside an atomic transaction: first delete related dependent records (
DELETE FROM messages WHERE device_id = ?;), then delete the parent entity (DELETE FROM devices WHERE id = ?;).
- Symptom: Malformed user-entered regex crashing
relayx-androidwith unhandledPatternSyntaxExceptionor causing ANRs (Application Not Responding) via catastrophic backtracking (ReDoS) during incoming SMS broadcast processing. - Root Cause: Compiling unvalidated user regex strings directly within the broadcast receiver or background thread without syntax validation or length constraints.
- Prevention: Validate regex patterns at entry time in UI/UseCase with explicit
try { Pattern.compile(pattern) } catch (e: PatternSyntaxException). Fall back to safe matching if compilation fails, log no payload text, and isolate evaluation in the pure domainRuleEngineverified by automated test suites.
- Symptom: Toggling between Light, Dark, or System theme dynamically in Settings leaves the status bar or navigation bar icons invisible (e.g. white icons against light backgrounds or dark icons against dark backgrounds) until app restart.
- Root Cause: Window insets controller properties (
isAppearanceLightStatusBars,isAppearanceLightNavigationBars) set statically during activity initialization rather than reactively responding to Compose theme state. - Prevention: Enforce a reactive
SideEffectinside the rootRelayxThemecomposable that queries the activedarkThemeboolean and explicitly synchronizesWindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkThemeandisAppearanceLightNavigationBars = !darkTheme.
- Symptom: Blocking MCP tool calls (
wait_for_message,get_otp) executed over HTTP/SSE transport fail immediately or return context errors (context canceled) before the target message arrives. - Root Cause: Passing the incoming HTTP POST request's
r.Context()to the background goroutine executing the tool. In Go'snet/http,r.Context()is canceled as soon as the HTTP 202 Accepted response is written and the handler returns. - Prevention: In asynchronous HTTP/SSE protocols where request acknowledgment and response delivery are decoupled, long-running tool execution contexts must be derived from the persistent
SSESessioncontext (session.ctx) which remains active for the full duration of the client connection. User.Context()solely for JSON parsing of the incoming POST body.