Skip to content

[Bug] conversation/templates request never resolves after the first call, permanently hanging ChatCompletionService.initConversationTemplates (CompletableFuture#get with no timeout) #370

Description

@nebosuke

Environment

  • OS: macOS (aarch64), Darwin 25.5.0
  • Eclipse Version: 4.40.0.20260604-0652 (Eclipse IDE for Enterprise Java and Web Developers), LSP4E 0.19.12.202605060644, Java (Eclipse Adoptium) 21.0.11
  • Plugin Version: 0.20.0.202607100822 (also reproduced on 0.20.0.202607100750_nightly); GitHub Copilot Language Server 1.509.5 (Node.js 22.22.3)

Describe the bug

After the very first conversation/templates LSP request succeeds, subsequent calls to the same method never receive a response from the GitHub Copilot Language Server, even though other request types sent in the same time window continue to resolve normally — so the LS process and JSON-RPC channel are not globally hung, only conversation/templates stops being answered after its first success.

Because ChatCompletionService.initConversationTemplates() calls CompletableFuture#get() with no timeout (and ModelService.fetchByokModels() has the identical pattern for listByokModels), the Eclipse Job worker thread that issued the request parks forever (WAITING on CompletableFuture$Signaller). Since Eclipse's Job.cancel() only flips an IProgressMonitor flag and cannot interrupt a thread already blocked inside CompletableFuture#get(), every time fetchAsync() fires again (e.g. on SKILL.md / *.prompt.md resource-change events) a new stuck worker thread accumulates and is never reclaimed. This has been observed to persist across Eclipse restarts — Eclipse's own shutdown handler logs "Job found still running after platform shutdown" for the same stuck job on repeated sessions.

Traced via decompilation of the plugin/LSP4E jars, the call chain has no timeout anywhere:

ChatCompletionService.initConversationTemplates()
  -> this.lsConnection.listConversationTemplates(workspaceFolders).get()      // NO TIMEOUT
CopilotLanguageServerConnection.listConversationTemplates()
  -> this.languageServerWrapper.execute(fn)                                   // LSP4E
LanguageServerWrapper.execute() -> executeImpl() -> getInitializedServer() -> start()
  // none of these apply any timeout to the eventual server response

Two compounding issues:

  1. Server-side: whatever handles conversation/templates inside the Copilot Language Server appears to stop responding to that method after the first successful call in a session (root cause not visible from the client side — needs LS-side investigation; looks like a debounce/memoization/in-flight-promise bug keyed by request method rather than by request id).
  2. Client-side: because .get() has no timeout anywhere in the call chain, and job cancellation cannot interrupt a thread already parked in .get(), a single unanswered request permanently strands a worker thread. Repeated triggers (e.g. many resource-change events in a multi-root workspace) accumulate multiple stuck threads per session.

To Reproduce

  1. Open Eclipse with a multi-root workspace containing custom SKILL.md / *.prompt.md files across several projects (this appears to increase the chance of fetchAsync() firing multiple times in quick succession during startup).
  2. Sign in to Copilot and let the workspace finish loading/indexing.
  3. Enable LSP4E tracing (Preferences > Language Servers > Logs, enable "Log to File" for server id com.microsoft.copilot.eclipse.ls) to capture the raw JSON-RPC traffic.
  4. Observe in the trace log that the first conversation/templates request/response pair succeeds:
    --> {"jsonrpc":"2.0","id":"4","method":"conversation/templates","params":{"workspaceFolders":[...]}}
    <-- {"jsonrpc":"2.0","id":"4","result":[{"id":"tests", ...}, ...]}   // full template list returned correctly
    
  5. Watch for later conversation/templates requests (fired again by fetchAsync()), e.g.:
    --> {"jsonrpc":"2.0","id":"24","method":"conversation/templates","params":{...}}   // never answered
    --> {"jsonrpc":"2.0","id":"32","method":"conversation/templates","params":{...}}   // never answered
    
    These never receive a matching response, while other concurrent request ids in the same window (e.g. id=23, id=17, id=14, id=31) resolve normally.
  6. Take a Java thread dump of the Eclipse process; a Worker-N: Refresh slash commands service thread is permanently WAITING inside ChatCompletionService.initConversationTemplates:
    "Worker-14: Refresh slash commands service" #102 prio=5 ... elapsed=247.05s ... waiting on condition
       java.lang.Thread.State: WAITING (parking)
            at jdk.internal.misc.Unsafe.park(Native Method)
            at java.util.concurrent.locks.LockSupport.park(LockSupport.java:221)
            at java.util.concurrent.CompletableFuture$Signaller.block(CompletableFuture.java:1864)
            at java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)
            at java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)
            at java.util.concurrent.CompletableFuture.waitingGet(CompletableFuture.java:1898)
            at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072)
            at com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService.initConversationTemplates(ChatCompletionService.java:126)
            at com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService$1.run(ChatCompletionService.java:99)
            at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
    

Expected behavior

conversation/templates should keep responding on every call for the lifetime of the session (or at minimum, the client should time out and recover instead of hanging a worker thread forever). Slash-command / template data in the chat panel should keep refreshing instead of silently freezin
g after the first successful fetch, and ChatCompletionService.isTempaltesReady() should not get permanently stuck once this happens.

Screenshots

N/A — this is a backend/threading hang with no visible UI error. Happy to attach the full sanitized LSP trace log and a Java thread dump on request.

Additional context

  • The exact same hang has recurred across multiple Eclipse restarts in our environment: at previous sessions' shutdown, Eclipse's own job manager logged the same stuck stack trace for ChatCompletionService$1, and — via the identical lsConnection.xxx().get() pattern with no timeout — for
    ModelService$1 (ModelService.fetchByokModels, ModelService.java:220) as well:
    !ENTRY org.eclipse.core.jobs 2 2 2026-07-24 06:29:30.048
    !MESSAGE Job found still running after platform shutdown. ...: com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService$1 RUNNING
          at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072)
          at com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService.initConversationTemplates(ChatCompletionService.java:126)
          at com.microsoft.copilot.eclipse.ui.chat.services.ChatCompletionService$1.run(ChatCompletionService.java:99)
          at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
    
    !ENTRY org.eclipse.core.jobs 2 2 2026-07-24 06:29:30.048
    !MESSAGE Job found still running after platform shutdown. ...: com.microsoft.copilot.eclipse.ui.chat.services.ModelService$1 RUNNING
          at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2072)
          at com.microsoft.copilot.eclipse.ui.chat.services.ModelService.fetchByokModels(ModelService.java:220)
          at com.microsoft.copilot.eclipse.ui.chat.services.ModelService$1.run(ModelService.java:176)
          at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
    
  • Suggested fixes:
    1. Investigate why the Copilot Language Server only answers the first conversation/templates request per session and silently drops subsequent ones.
    2. On the extension side, add a bounded timeout (e.g. .get(30, TimeUnit.SECONDS)) to the blocking calls in ChatCompletionService.initConversationTemplates and ModelService.fetchByokModels (and audit other similar lsConnection.xxx().get() call sites), logging/reporting a timeout ins
      tead of hanging forever.
    3. Make fetchAsync()'s job cancellation actually propagate to the in-flight CompletableFuture (e.g. keep a reference and call .cancel(true) on it) instead of only cancelling the IProgressMonitor, which has no effect on a thread already inside .get().

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions