From 254acc92a4fdf782404e7fb807a0bbea5e7b3287 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 18 Aug 2026 12:23:23 -0400 Subject: [PATCH] fix(jetbrains): don't surface workspace fetch failures as IDE errors Retry exhaustion for workspace/app data loads logged via IntelliJ's Logger.error, which IntelliJ turns into an 'IDE Internal Error' popup. Downgrade the exhausted-retry log to warn and enrich workspace fetch failures with HTTP status and response body so the real cause (e.g. a 500 from the CLI /agent endpoint) is visible in diagnostics instead of a generic crash-style dialog. --- .../quiet-jetbrains-workspace-errors.md | 5 ++ .../backend/app/KiloBackendAppService.kt | 2 +- .../backend/workspace/KiloBackendWorkspace.kt | 51 +++++++++++++++++-- .../backend/app/KiloBackendAppServiceTest.kt | 2 + .../workspace/KiloBackendWorkspaceTest.kt | 7 +++ 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 .changeset/quiet-jetbrains-workspace-errors.md diff --git a/.changeset/quiet-jetbrains-workspace-errors.md b/.changeset/quiet-jetbrains-workspace-errors.md new file mode 100644 index 0000000000..1b8eb82df9 --- /dev/null +++ b/.changeset/quiet-jetbrains-workspace-errors.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Avoid showing JetBrains internal error popups when Kilo workspace data fails to load, and include HTTP status and response details in diagnostics. diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt index 0318e49d11..3047be2a7d 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt @@ -786,7 +786,7 @@ class KiloBackendAppService private constructor( delay(RETRY_DELAY_MS) } } - log.error("$name: all $MAX_RETRIES attempts failed") + log.warn("$name: all $MAX_RETRIES attempts failed${last.error?.let { ": ${error(it)}" } ?: ""}") return last } diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt index 27e19d1f06..925b5d29e3 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt @@ -6,6 +6,10 @@ import ai.kilocode.backend.app.SseEvent import ai.kilocode.backend.cli.KiloCliDataParser import ai.kilocode.log.KiloLog import ai.kilocode.jetbrains.api.client.DefaultApi +import ai.kilocode.jetbrains.api.infrastructure.ClientError +import ai.kilocode.jetbrains.api.infrastructure.ClientException +import ai.kilocode.jetbrains.api.infrastructure.ServerError +import ai.kilocode.jetbrains.api.infrastructure.ServerException import ai.kilocode.jetbrains.api.model.Agent import ai.kilocode.rpc.dto.SessionDto import ai.kilocode.rpc.dto.SessionListDto @@ -231,7 +235,8 @@ class KiloBackendWorkspace( } catch (e: CancellationException) { throw e } catch (e: Exception) { - log.warn("Agents fetch failed: ${e.message}", e) + log.warn("Agents fetch failed: ${detail(e)}", e) + logResponseBody("agents", e) FetchResult.fail("agents", e) } } @@ -260,7 +265,8 @@ class KiloBackendWorkspace( } catch (e: CancellationException) { throw e } catch (e: Exception) { - log.warn("Skills fetch failed: ${e.message}", e) + log.warn("Skills fetch failed: ${detail(e)}", e) + logResponseBody("skills", e) FetchResult.fail("skills", e) } } @@ -301,19 +307,34 @@ class KiloBackendWorkspace( delay(RETRY_DELAY_MS) } } - log.error("$name: all $MAX_RETRIES attempts failed") + log.warn("$name: all $MAX_RETRIES attempts failed${last.error?.let { ": ${error(it)}" } ?: ""}") return last } private fun setWorkspaceError(message: String, errors: List) { - log.warn("Workspace error [$directory]: $message") + val text = if (errors.isEmpty()) message else "$message [${errors.joinToString("; ") { error(it) }}]" + log.warn("Workspace error [$directory]: $text") _state.value = KiloWorkspaceState.Error(message, errors) } + private fun logResponseBody(resource: String, e: Exception) { + val body = body(e) ?: return + log.warn("$resource response body: $body") + } + + private fun error(err: LoadError): String { + val status = err.status?.let { " status=$it" } ?: "" + val detail = err.detail?.let { " detail=$it" } ?: "" + return "${err.resource}$status$detail" + } + private data class FetchResult(val value: T?, val error: LoadError?) { companion object { fun ok(value: T) = FetchResult(value, null) - fun fail(resource: String, e: Exception? = null) = FetchResult(null, LoadError(resource, detail = e?.message)) + fun fail(resource: String, e: Exception? = null) = FetchResult( + null, + LoadError(resource, status = e?.let(::status), detail = e?.let(::detail)), + ) } } @@ -322,3 +343,23 @@ class KiloBackendWorkspace( } private fun encode(value: String) = java.net.URLEncoder.encode(value, Charsets.UTF_8) + +private fun status(e: Exception): Int? = when (e) { + is ClientException -> e.statusCode + is ServerException -> e.statusCode + else -> null +} + +private fun body(e: Exception): String? = when (e) { + is ClientException -> (e.response as? ClientError<*>)?.body?.toString() + is ServerException -> (e.response as? ServerError<*>)?.body?.toString() + else -> null +} + +private fun detail(e: Exception): String? { + val body = body(e) + val status = status(e) + if (status != null && !body.isNullOrBlank()) return "HTTP $status: $body" + if (status != null) return "HTTP $status: ${e.message}" + return e.message +} diff --git a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt index f059fa8e0c..8f5ded0924 100644 --- a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt +++ b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/app/KiloBackendAppServiceTest.kt @@ -522,6 +522,8 @@ class KiloBackendAppServiceTest { assertTrue(log.messages.any { it.contains("App error: Failed to load required data") && it.contains("config") }) + assertTrue(log.messages.any { it.contains("WARN: config: all 3 attempts failed") }) + assertTrue(log.messages.none { it.contains("ERROR: config: all 3 attempts failed") }) } @Test diff --git a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspaceTest.kt b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspaceTest.kt index 6104fed10e..c8dbb68436 100644 --- a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspaceTest.kt +++ b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspaceTest.kt @@ -232,6 +232,7 @@ class KiloBackendWorkspaceTest { @Test fun `agents failure retries then transitions to Error`() = runBlocking { mock.agentsStatus = 500 + mock.agents = """{"error":"invalid agent config"}""" val app = setup() val ws = ready(app) @@ -241,6 +242,12 @@ class KiloBackendWorkspaceTest { val err = ws.state.value as KiloWorkspaceState.Error assertTrue(err.message.contains("agents")) + val item = err.errors.single { it.resource == "agents" } + assertEquals(500, item.status) + assertTrue(item.detail?.contains("invalid agent config") == true) + assertTrue(log.messages.any { it.contains("agents response body:") && it.contains("invalid agent config") }) + assertTrue(log.messages.any { it.contains("WARN: agents: all 3 attempts failed") }) + assertTrue(log.messages.none { it.contains("ERROR: agents: all 3 attempts failed") }) } @Test