.kilo/plans/1786990849107-jetbrains-devcontainer-unsupported-notice.md
When a JetBrains project is opened so that its directory is a local-IDE + virtual (IJent) path — e.g. /$devcontainer.ij/<id>@…podman.sock/… (the "Model 2" case) — the host-side Kilo CLI cannot resolve the directory, so agent resolution returns HTTP 500 and the workspace fails to load. Today this surfaces as a generic red "Workspace loading failed" banner with a futile "Try again".
Replace that with a clear, non-error notice that:
Detection short-circuits workspace load before the 3× /agent 500 retries.
packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/workspace/KiloBackendWorkspace.kt (load() fetches agents/providers/commands/skills; failure → KiloWorkspaceState.Error)..../backend/workspace/KiloWorkspaceState.kt (Pending/Loading/Ready/Error).packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/KiloWorkspaceStateDto.kt (KiloWorkspaceStatusDto = PENDING/LOADING/READY/ERROR).when over sealed state): .../backend/rpc/KiloWorkspaceRpcApiImpl.kt dto(state) (~line 493). Directory resolution resolveProjectDirectory returns project.basePath; localConfig (~line 343) already throws InvalidPathException for IJent paths..../frontend/.../session/controller/SessionController.kt resolveConnectionState() (~line 2270) maps workspace.status == ERROR → ConnectionChanged.ShowError; retryConnection reloads the workspace when status is ERROR (~line 622)..../frontend/.../session/ui/ConnectionPanel.kt (red/warning label + expandable details + "Try again" ActionLink). Events defined in .../session/controller/SessionControllerEvent.kt (ConnectionChanged.{Hide,ShowConnecting,ShowDownloading,ShowError,ShowWarning})..../frontend/src/main/resources/messages/KiloBundle.properties (session.connection.*). Only the base bundle needs new keys; other locales fall back.fix(jetbrains): don't surface workspace fetch failures as IDE errors); genuine 500s from real directories keep the existing Error path.ERROR, so the UI is informational (not red), retry is suppressed, and there is no 500/log/retry spam.READY (providers/config are global and unaffected)./$devcontainer.ij/, or\\wsl$\ or \\wsl.localhost\, orjava.nio.file.Path.of(directory) (or normalize) throws InvalidPathException.
Do not trigger solely on "path doesn't exist" (avoids false positives on transient FS states / real paths). Real container paths (Model 1, e.g. /workspaces/podman) and normal local paths never match.https://kilo.ai/docs/jetbrains/dev-containers (see Open items — confirm/replace). Keep the URL as one constant so it's trivial to change.KiloBundle.properties only.shared/.../rpc/dto/KiloWorkspaceStateDto.kt: add UNSUPPORTED to KiloWorkspaceStatusDto. Reuse the existing error: String? field to carry a short reason code/message (no new field required); keep errors empty for this state.backend/.../workspace/KiloWorkspaceState.kt: add data class Unsupported(val reason: String) : KiloWorkspaceState().RemoteDirectory.detect(directory: String): String? returning a reason string when the directory is an unsupported virtual/IJent path, else null. Place it in backend/.../workspace/ (backend-owned; no kilocode_change marker needed — new Kilo file in the Kilo plugin).backend/.../workspace/KiloBackendWorkspace.kt: at the very start of load(), if RemoteDirectory.detect(directory) != null, set _state.value = KiloWorkspaceState.Unsupported(reason), log a single info/warn line (not error), and return without fetching. Ensure no fetch/retry runs.backend/.../rpc/KiloWorkspaceRpcApiImpl.kt dto(state): add branch is KiloWorkspaceState.Unsupported -> KiloWorkspaceStateDto(status = UNSUPPORTED, error = state.reason).frontend/.../session/controller/SessionControllerEvent.kt: add data class ShowNotice(val summary: String, val detail: String?, val learnMoreUrl: String? = null) : ConnectionChanged().frontend/.../session/controller/SessionController.kt:
resolveConnectionState(): add a branch for workspace.status == KiloWorkspaceStatusDto.UNSUPPORTED (place before the generic ERROR branch) returning ShowNotice(summary=…, detail=…, learnMoreUrl=…) using new bundle keys.retryConnection() / retry path (~line 622): do not call workspace.reload() when status is UNSUPPORTED.WorkspaceChanged handling (~line 933) already no-ops for non-READY (it does).frontend/.../session/ui/ConnectionPanel.kt: handle ConnectionChanged.ShowNotice:
errorLabelForeground).detail in the expandable area if used.HyperlinkLabel/ActionLink that opens learnMoreUrl with BrowserUtil.browse(url).frontend/.../messages/KiloBundle.properties: add keys, e.g.:
session.connection.notice.devcontainer.summary=Kilo can't access this Dev Container projectsession.connection.notice.devcontainer.detail=This project is opened through a Dev Container/remote virtual filesystem that the Kilo runtime on your machine can't reach. Run Kilo inside the container using JetBrains Remote Development (the IDE backend runs in the container) — that's the recommended way. Local projects also work.session.connection.notice.learnMore=Learn morebackend/src/test/.../workspace/KiloBackendWorkspaceTest.kt:
/$devcontainer.ij/abc@…podman.sock/IdeaProjects/x, workspace state becomes Unsupported; assert no /agent request hit the mock CLI (via MockCliServer request log), no retries, and no ERROR/500 log lines.\\wsl$\Ubuntu\home\x case and an InvalidPathException-triggering case./workspaces/...-style dir still load normally (existing tests cover normal load).Unsupported → KiloWorkspaceStateDto(status=UNSUPPORTED, error=reason).frontend/src/test/.../session/ui/ConnectionPanelTest.kt: ShowNotice renders info label (not error color), shows the summary, exposes the "Learn more" link, and hides retry.session/controller/…): KiloWorkspaceStatusDto.UNSUPPORTED state produces a ConnectionChanged.ShowNotice, and retryConnection() does not call workspace.reload() for UNSUPPORTED..changeset/<slug>.md ("@kilocode/kilo-jetbrains": patch) describing the user-facing behavior: "Show a clear notice (with guidance to run in a Dev Container) instead of a generic error when a project is opened through a Dev Container/remote virtual filesystem Kilo can't access."models, file search, git) for virtual paths; they already fail soft. The banner communicates the root cause.InvalidPathException detection (not "not exists")./workspaces/...), markers don't match → normal load.when in backend dto() (compile-enforced). Frontend status checks are equality-based; add the UNSUPPORTED branch in resolveConnectionState and confirm no other exhaustive when(status) needs a branch (grep KiloWorkspaceStatusDto.).packages/kilo-jetbrains/: ./gradlew :backend:test --tests ai.kilocode.backend.workspace.KiloBackendWorkspaceTest and the new frontend tests (./gradlew :frontend:test --tests …ConnectionPanelTest and the controller test).packages/kilo-jetbrains/: bun run typecheck (or ./gradlew typecheck) and ./gradlew test.https://kilo.ai/docs/jetbrains/dev-containers. Confirm the final path or point to an existing page; may require creating that docs page (in packages/kilo-docs/) separately. If source URLs under packages/kilo-vscode/opencode change this doesn't apply, but if a docs page is added, run bun run script/extract-source-links.ts only if a tracked source URL changes.capture(...) pattern in SessionController when the notice is first shown. Recommended: include it; low cost.