@@ -63,6 +63,12 @@ class OpencodeCore(
6363 // Tracks the in-flight probeHealth coroutine. Rapid double-taps on Connect would
6464 // otherwise launch two concurrent health probes; cancel the prior one first.
6565 private var connectJob: Job? = null
66+ // Tracks the in-flight DeleteSession coroutine so navigating away can cancel it
67+ // before its response handler mutates the sessions list / screen the user already left.
68+ private var deleteSessionJob: Job? = null
69+ // Tracks the in-flight sendMessage coroutine so navigating away or switching sessions
70+ // can cancel it before its response handler touches stale message state.
71+ private var sendMessageJob: Job? = null
6672 // Message IDs removed via SSE; a late MessageUpdated for one must not resurrect it.
6773 private val removedMessageIds = mutableSetOf<String>()
6874
@@ -111,6 +117,7 @@ class OpencodeCore(
111117 generating = false
112118 error = null
113119 emit()
120+ sendMessageJob?.cancel()
114121 loadMessagesJob?.cancel()
115122 loadMessagesJob = scope.launch { loadMessages(event.id) }
116123 }
@@ -126,7 +133,8 @@ class OpencodeCore(
126133 // Delete on the server first; only drop it from the UI once that
127134 // succeeds, so a failed request leaves the session recoverable.
128135 loading = true; error = null; emit()
129− scope.launch {
136+ deleteSessionJob?.cancel()
137+ deleteSessionJob = scope.launch {
130138 http.delete("$serverUrl/session/$id", authHeader()).fold(
131139 onSuccess = { resp ->
132140 if (resp.code in 200..299) {
@@ -171,7 +179,10 @@ class OpencodeCore(
171179 generating = false
172180 loading = true
173181 emit()
182+ connectJob?.cancel()
174183 createSessionJob?.cancel()
184+ deleteSessionJob?.cancel()
185+ sendMessageJob?.cancel()
175186 loadMessagesJob?.cancel()
176187 loadSessionsJob?.cancel()
177188 loadSessionsJob = scope.launch { loadSessions() }
@@ -190,7 +201,10 @@ class OpencodeCore(
190201 // Reset to the default so a late-firing close callback from the old
191202 // SSE connection can't flash the reconnecting banner on the next connect.
192203 sseConnected = true
204+ connectJob?.cancel()
193205 createSessionJob?.cancel()
206+ deleteSessionJob?.cancel()
207+ sendMessageJob?.cancel()
194208 loadMessagesJob?.cancel()
195209 closeSse()
196210 emit()
@@ -230,7 +244,8 @@ class OpencodeCore(
230244 emit()
231245
232246 val body = Protocol.json.encodeToString(PromptBody.serializer(), PromptBody(listOf(TextInput(text = text))))
233− scope.launch {
247+ sendMessageJob?.cancel()
248+ sendMessageJob = scope.launch {
234249 val result = http.postJson("$serverUrl/session/$session/prompt_async", body, authHeader())
235250 result.fold(
236251 onSuccess = { resp ->
@@ -284,6 +299,7 @@ class OpencodeCore(
284299 }
285300 else -> {
286301 loading = false
302+ connected = false
287303 error = "Server returned status ${resp.code}"
288304 emit()
289305 }
@@ -291,6 +307,7 @@ class OpencodeCore(
291307 },
292308 onFailure = { e ->
293309 loading = false
310+ connected = false
294311 error = "Connection failed: ${e.message}"
295312 emit()
296313 },