theonedev/onedev · warning · ExplicitException

Conversation context lost

Error message

Conversation context lost

What it means

OneDev's AI chat service streams tool calls from the LLM back to the browser over a WebSocket. When the model returns tool execution requests, the service looks up the originating WebSocket connection by session id and page key; if the connection is gone or closed, it aborts with this ExplicitException because the tool result can no longer be delivered to the client.

Solutions

  1. Keep the chat page open until the AI response finishes; check the browser console for WebSocket disconnects before retrying
  2. Retry the conversation from a fresh chat page so a new WebSocket connection and pageKey are established
  3. If behind a reverse proxy, raise proxy read/send timeouts and enable WebSocket upgrade passthrough
  4. Check server logs for earlier WebSocket errors (connection registry drops) and adjust WebSocket idle timeout settings

Example fix

// before: user closes page mid-stream -> exception
// after: client keeps the chat panel mounted and reconnects before sending a message
// (server-side guard in calling code)
try {
    chatService.chat(...);
} catch (ExplicitException e) {
    if (e.getMessage().equals("Conversation context lost")) {
        reconnectWebSocketAndResend();
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side before sending a chat message
if (!ws || ws.readyState !== WebSocket.OPEN) reconnectChatSocket();

Try / catch

try {
    chatService.chat(chatId, prompt);
} catch (ExplicitException e) {
    if ("Conversation context lost".equals(e.getMessage())) {
        // reopen chat page / re-establish WebSocket and resend
    }
}

Prevention

When it happens

Trigger: onCompleteResponse receives an AI message containing tool execution requests while connectionRegistry.getConnection(application, sessionId, pageKey) returns null or a closed connection — i.e. the user closed/refreshed the chat page or the WebSocket dropped mid-conversation.

Common situations: User navigates away or reloads the page while the AI response is still streaming; WebSocket idle timeouts or proxy/load-balancer disconnects; server restart between request and tool-call phase; session id or page key changed after a page re-render.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/41d488616661a4a6. Report an issue: GitHub.

Appendix: source

Thrown at server-core/src/main/java/io/onedev/server/ai/DefaultChatService.java:317

					@Override
					public void onCompleteResponse(ChatResponse completeResponse) {	
						ThreadContext.bind(subject);
						var responding = getResponding(sessionId, chatId, requestId);
						if (responding != null && responding.getContent() != null && thinkingBlockOpen) {
							responding.content = ensureThinkingClosed(responding.getContent());
							webSocketService.notifyObservableChange(Chat.getPartialResponseObservable(chatId), null);
						}
						sessionService.runAsync(() -> {
							try {
								var aiMessage = completeResponse.aiMessage();
								if (aiMessage.hasToolExecutionRequests()) {
									langchain4jMessages.add(aiMessage);
									var toolRequests = aiMessage.toolExecutionRequests();
									var connectionRegistry = WebSocketSettings.Holder.get(application).getConnectionRegistry();
									var connection = connectionRegistry.getConnection(application, sessionId, pageKey);
									if (connection == null || !connection.isOpen())
										throw new ExplicitException("Conversation context lost");
									for (var toolRequest: toolRequests) {
										if (responseFuture.isDone())										
											return;
										String toolName = toolRequest.name();
										try {
											var toolExecution = new AiToolExecution(toolName, ToolUtils.getToolArguments(toolRequest));
											connection.sendMessage(toolExecution);
											var toolExecutionFuture = toolExecution.getFuture();
											if (toolExecutionFuture == null)
												throw new ExplicitException("Tool not found: " + toolName);
											while (true) {
												try {
													var toolExecutionResult = toolExecutionFuture.get(1, TimeUnit.SECONDS);
													toolExecutionResult.addToMessages(langchain4jMessages, toolRequest);
													break;
												} catch (TimeoutException e) {
													if (responseFuture.isDone())
														return;

View on GitHub (pinned to d44925c47c)