Summary
With a tethered (ADB) data connection established, the Connect IQ simulator crashes with SIGSEGV the moment a watch app calls Communications.transmit. No byte is written to the socket. Reproducible with a one-line addition to your own samples/Comm, on both SDK 9.1.0 and 9.2.0.
The opposite direction — phone → watch — works correctly over the very same connection, so the transport and the session are demonstrably healthy when the crash happens.
Environment
- SDK 9.1.0 (2026-03-09) and 9.2.0 (2026-06-09) — both affected
- Linux (Ubuntu 22.04 container on a Fedora host)
- Devices tried:
vivoactive3(Comm sample),fr965(our own app) - Peer: a plain TCP server on
127.0.0.1:7381, in place ofadb forward tcp:7381 tcp:7381
Minimal reproduction
- Listen on TCP
127.0.0.1:7381. Any server will do — this is the host side ofadb forward tcp:7381 tcp:7381, and the simulator is the client. Keep it listening for the whole session. - Start the simulator, then Connection → start the ADB connection. The simulator connects.
- Take
samples/Communmodified and add one line toCommApp.onStart:
public function onStart(state as Dictionary?) as Void {
Communications.transmit("Hello World.", null, new $.CommListener());
}
- Run it.
Expected: the payload is serialized and written to the socket.
Actual: the simulator dies with SIGSEGV within a second. Zero bytes are written.
Left unmodified — the sample only transmits from its menu — the same binary stays alive indefinitely while connected.
What we verified is NOT the cause
Each of these was tested and crashes identically:
- Payload type —
String(Comm sample) vsDictionary(our app): both crash. - The
optionsargument —null(as your sample passes) vs{}(as the docs prescribe): both crash. - Stale or invalid session — transmit on a session where a phone → watch message had just been accepted and displayed: crashes.
- Our own code — reproduces on
samples/Commplus one line. - A recent regression — 9.1.0 and 9.2.0 both crash.
Crash analysis
Identical stack across our app and the patched sample (stripped binary, offsets from the ELF base):
#0 simulator + 0xd5fcc8 #1 simulator + 0x40d967 #2 simulator + 0x396ba6
The faulting function:
movl $0x0,(%rsi) ; *out = 0 test %rdi,%rdi je <return> ; guard 1: null object -> clean return call <...> ; ~ buf->data() mov %rax,%r12 call <...> ; ~ buf->size() cmp $0x3,%eax jbe <bail> ; guard 2: size <= 3 -> clean return => movzwl (%r12),%edx ; SIGSEGV movzbl 0x2(%r12),%ecx rol $0x8,%dx shl $0x8,%edx or %ecx,%edx ; 24-bit big-endian length
Both guards pass — the object is non-null and reports size > 3 — yet %r12 holds 0x8e432fa067e9a719, which is not a mapped address. data() therefore returns a wild pointer while size() returns a plausible value: the buffer object is in an inconsistent state (use-after-free, or read before initialisation). Nothing has been received on the socket at that point, so this is not parsing of peer input.
The opposite direction works, which makes this a narrow bug
To be certain the harness was faithful, we implemented the phone side using the serialization classes shipped in your own companion SDK (ciq-companion-app-sdk, com.garmin.monkeybrains.serialization). Sending a correctly serialized object to the simulator over the same socket works: registerForPhoneAppMessages fires and samples/Comm displays the payload, and the connection stays open.
So: phone → watch is fine; watch → phone segfaults.
Additional observations (same transport, probably related, listed for completeness)
- If the peer stops listening, the simulator crashes rather than reporting a dropped link.
- After a few connect/reject cycles, the simulator dies with SIGABRT instead (stack entirely in libc) — a different site.
- Occasionally the simulator freezes instead of crashing (no coredump, the process must be killed).
- On both SDKs, the
bin/connectiqlauncher aborts (SIGABRT) at startup on this machine, while runningbin/simulatordirectly works. Possibly unrelated; mentioned in case it helps. - A malformed payload sent phone → watch is answered by an immediate connection close. We believe this explains the long-standing "Get Comm sample working" report ("ADB Disconnected" when messaging the watch): the payload was not a valid serialized object, and the close is the simulator’s rejection — not a transport failure.
Impact
Phone ↔ watch messaging cannot be exercised in the simulator in the watch → phone direction. For developers targeting devices they do not own, the simulator is the only option, so this blocks testing of any app that talks to a companion app.