Skip to content

Commit a1fd391

Browse files
committed
fix: fail fast when Java debug prerequisites are not ready
1 parent 5586223 commit a1fd391

18 files changed

Lines changed: 979 additions & 246 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ The debugger will automatically attach. See [No-Config Debug Documentation](bund
6262

6363
No-Config Debug is enabled by default. To disable the terminal integration and the AI `debug_java_application` tool, set `"java.debug.settings.enableNoConfigDebug": false`, reload VS Code, and recreate existing terminals. Standard Java launch/attach debugging, including F5 and Run/Debug CodeLens, remains available.
6464

65-
No-Config Debug prepares its terminal integration in the background without delaying core Run/Debug registration. The AI launch tool waits for it to be ready (up to 60 seconds, cancellable). A terminal opened before preparation finishes may need to be recreated to receive the environment contributions.
65+
No-Config Debug prepares its terminal integration in the background without delaying core Run/Debug registration. The AI launch tool returns immediately with `JAVA_NOT_READY` while JDT LS is starting, or `NO_CONFIG_NOT_READY` while terminal preparation is incomplete. No launch is attempted or queued; retry after the reported prerequisite is ready. A terminal opened before preparation finishes may need to be recreated to receive the environment contributions.
6666

6767
## AI-Assisted Debugging
6868

bundled/agents/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ Make sure the Java project is properly loaded. Check that:
167167

168168
The `debug_java_application` tool requires `java.debug.settings.enableNoConfigDebug` (enabled by default). If you disable this setting, reload VS Code and recreate existing terminals. The launch tool then returns an explanatory message without running `debugjava`; tools that inspect or control existing debug sessions remain available.
169169

170-
The launch tool also waits for No-Config Debug initialization to finish before building, creating a terminal, or stopping an existing session. This wait is cancellable and limited to 60 seconds. A timeout does not stop background initialization; you can retry later. Initialization failures are reported without attempting to launch.
170+
The launch tool checks Java and No-Config readiness without waiting for initialization. `JAVA_NOT_READY` means JDT LS has not reported ready; `NO_CONFIG_NOT_READY` means Java is ready but terminal preparation is incomplete. No build, terminal, or debug session changes are made, and no launch is queued. Report the prerequisite to the user or continue independent work; invoke the tool again only after it becomes ready, rather than polling or changing project code.
171+
172+
`JAVA_INIT_FAILED` and `NO_CONFIG_INIT_FAILED` identify initialization failures, not application failures. Follow their recovery guidance instead of diagnosing output from an older debug terminal or bypassing readiness with a terminal launch. Disabled integration, cancellation, and disposal are also reported explicitly.
171173

172174
Ensure:
173175
- Your project compiles successfully

bundled/agents/debug.agent.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,19 @@ vscjava.vscode-java-debug/getDebugSessionInfo() // Check again - should now be
178178

179179
### 2.4 Automatic Cleanup on Restart
180180

181-
**Good news:** The `debugJavaApplication` tool automatically cleans up before starting:
181+
**Good news:** Once Java and No-Config preparation are ready, the `debugJavaApplication` tool automatically cleans up before starting:
182182
- Stops any existing Java debug session (avoids JDWP port conflicts)
183183
- Closes existing "Java Debug" terminals (avoids confusion)
184184

185185
This means you can safely call `debugJavaApplication` again without manually stopping the previous session. The tool handles cleanup for you.
186186

187187
### 2.5 Fallback: When debugJavaApplication Fails or Times Out
188188

189-
When `debugJavaApplication` returns timeout or failure, follow this recovery workflow:
189+
If `debugJavaApplication` returns `JAVA_NOT_READY` or `NO_CONFIG_NOT_READY`, no launch was attempted and existing sessions and terminals were left unchanged. Explain the prerequisite to the user or continue independent work, then retry only after readiness changes. Do not poll the launch tool, change project code, read an old terminal as evidence of a new launch failure, or bypass readiness with a terminal launch.
190+
191+
For `JAVA_INIT_FAILED`, `NO_CONFIG_INIT_FAILED`, or `NO_CONFIG_DISABLED`, follow the returned initialization or setting guidance rather than the application-error workflow below.
192+
193+
When `debugJavaApplication` returns an actual launch timeout or failure, follow this recovery workflow:
190194

191195
**Step 1: Check terminal output for errors**
192196
```

bundled/scripts/noConfigScripts/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ Note: `JAVA_TOOL_OPTIONS` is NOT set globally to avoid affecting other Java tool
1515

1616
The extension registers core Java Run/Debug support first, then starts No-Config Debug initialization in the background. Ordinary launch/attach registration and extension activation do not wait for endpoint storage, Java executable discovery, or wrapper permission preparation.
1717

18-
The AI `debug_java_application` tool waits for the shared initialization task before inspecting the launch input, building, creating a terminal, or stopping an existing debug session. Each wait is cancellable and limited to 60 seconds. Cancelling or timing out one invocation does not cancel initialization or another invocation's wait; a later invocation can retry. Initialization failure or extension disposal returns an explanatory result rather than attempting a launch.
18+
The AI `debug_java_application` entry is registered immediately. It observes the Java extension's `serverReady()` signal in the background, independently of terminal preparation. Invocation checks both states before inspecting launch inputs, probing Java, recording launch telemetry, building, creating a terminal, or stopping an existing debug session. It never waits for startup or queues a launch.
19+
20+
If JDT LS is not ready, the tool immediately returns `JAVA_NOT_READY`. If Java is ready but terminal preparation is incomplete, it returns `NO_CONFIG_NOT_READY`. Wait for the reported prerequisite before a new invocation; do not retry in a loop or treat readiness as a project-code error. Becoming ready does not automatically launch a previously refused request. Known Java or No-Config initialization failures, disabled integration, cancellation, and disposal return distinct explanations.
21+
22+
The existing `javaLSReady` tool visibility condition remains unchanged. Direct tool calls still receive explicit readiness feedback. Endpoint listeners remain eager rather than waiting for JDT LS, so surviving terminals can submit endpoints while Java starts. Directory preparation and startup cleanup run once per registration, not once per tool invocation.
1923

2024
This is not lazy terminal setup: preparation still starts during activation. However, activation completing does not guarantee that `debugjava` is ready. Terminals opened before preparation finishes may lack the environment contributions and must be recreated afterward. Existing terminals are not automatically closed or repaired.
2125

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,7 @@
10171017
{
10181018
"name": "debug_java_application",
10191019
"displayName": "Debug Java Application",
1020-
"modelDescription": "Launch or attach to a Java application in debug mode with automatic compilation and classpath resolution. The tool handles building the project, resolving dependencies, starting the JVM with JDWP enabled, and auto-attaching the VS Code debugger. Use this as the first step to establish a debug session. The debug process runs in the background until stopped. Example usage: Debug a main class ('com.example.Main'), a JAR file ('target/app.jar'), or with program arguments (['--port=8080']).",
1020+
"modelDescription": "Launch or attach to a Java application in debug mode with automatic compilation and classpath resolution. The tool handles building the project, resolving dependencies, starting the JVM with JDWP enabled, and auto-attaching the VS Code debugger. Use this as the first step to establish a debug session. Returns JAVA_NOT_READY or NO_CONFIG_NOT_READY immediately if startup is incomplete, without attempting or queuing a launch. Retry only after readiness changes; do not poll or change project code for a readiness result. The debug process runs in the background until stopped. Example usage: Debug a main class ('com.example.Main'), a JAR file ('target/app.jar'), or with program arguments (['--port=8080']).",
10211021
"toolReferenceName": "debugJavaApplication",
10221022
"tags": [
10231023
"java",

resources/instruments/javaDebugContext.instructions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ For Java run/launch/debug/inspection requests, prefer the Java debug language mo
1313

1414
If both apply (e.g. "launch and break on entry of `Main.foo`"), load `java-launch-troubleshooting` first, then `java-debug-inspection` after the session is active.
1515

16-
Fall back to `run_in_terminal` only when `debug_java_application` returns "Java Language Server not ready" or "project not detected".
16+
If `debug_java_application` returns `JAVA_NOT_READY` or `NO_CONFIG_NOT_READY`, no launch was attempted. Report the startup prerequisite or continue independent work, then retry only after readiness changes. Do not poll, modify project code, or use a terminal launch to bypass readiness. Follow the returned recovery guidance for initialization failures or disabled integration.
17+
18+
Fall back to `run_in_terminal` when `debug_java_application` returns "project not detected".

resources/skills/java-launch-troubleshooting/SKILL.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ These language model tools are contributed by the `Debugger for Java` extension
2727
1. **Confirm intent.** Is the user trying to *run / start / launch / stop* a Java program (use this skill) or just edit code (do not load this skill)?
2828
2. **Check existing session.** Call `get_debug_session_info` first. If a session is already running for the target, do not launch a second one.
2929
3. **Launch.** Call `debug_java_application` with `target` = the fully qualified main class or JAR, and `workspacePath` = the project root containing `pom.xml`, `build.gradle`, or `.classpath`. Let `skipBuild` default to `false` so the tool handles compilation.
30-
4. **Read the error.** If `debug_java_application` fails, the error message is structured (mainClass missing, classpath unresolved, build failure with line number). Use it to suggest a fix — do not retry with `run_in_terminal`.
30+
4. **Read the result.** `JAVA_NOT_READY` and `NO_CONFIG_NOT_READY` are startup prerequisites, not project errors: no launch was attempted or queued. Report the prerequisite or continue independent work, and retry only after readiness changes. Do not poll, modify project code, or bypass readiness with `run_in_terminal`. For actual launch errors (mainClass missing, classpath unresolved, build failure with line number), use the reported details to suggest a fix.
3131
5. **Stop when done.** When the user says "stop", "kill it", or has the answer they need, call `stop_debug_session`.
3232

3333
## Common Failure Modes
3434

3535
| Symptom from `debug_java_application` | Likely cause | Suggested fix |
3636
|---|---|---|
37+
| `JAVA_NOT_READY` | JDT LS has not reported ready, or Standard mode/project import has not started | Wait for Java initialization; in Lightweight/manual-import mode, switch to Standard mode or import the project before retrying |
38+
| `NO_CONFIG_NOT_READY` | Java is ready but the terminal integration is still being prepared | Retry after preparation completes; do not repeat the call in a loop |
39+
| `JAVA_INIT_FAILED` / `NO_CONFIG_INIT_FAILED` / `NO_CONFIG_DISABLED` | A startup prerequisite failed or the integration is disabled | Follow the returned initialization or setting guidance, not application-error recovery |
3740
| `mainClass is not configured` / `mainClass missing` | Project has no `launch.json`, and the file has no `public static void main` | Ask user which class to launch, or generate `launch.json` |
3841
| `Could not resolve classpath` | Maven/Gradle import has not completed, or `pom.xml` has unresolved dependencies | Wait for Java Language Server import, then ask user to run `Java: Clean Java Language Server Workspace` |
3942
| `Compilation failed` with file:line | Source code has a compile error | Fix the reported error in the source file, do not retry the launch |
@@ -47,4 +50,4 @@ These language model tools are contributed by the `Debugger for Java` extension
4750

4851
## Fallback
4952

50-
If `debug_java_application` returns `Java Language Server not ready` or repeats the same error twice, fall back to `run_in_terminal` with the appropriate `mvn` or `gradle` command and report the raw output to the user. Do not retry the debug tool more than twice.
53+
For an actual application launch error that repeats twice, fall back to `run_in_terminal` with the appropriate `mvn` or `gradle` command and report the raw output to the user. This fallback does not apply to readiness, initialization, disabled, cancelled, or disposed results. Do not retry the debug tool more than twice for the same application error.

src/extension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ export async function deactivate() {
111111
const delay = promisify(setTimeout);
112112

113113
/**
114-
* Register tools when the Java extension is installed. The launch tool waits
115-
* for No-Config Debug readiness at invocation, not during core activation.
114+
* Register tools when the Java extension is installed. The launch tool checks
115+
* Java and No-Config readiness at invocation without waiting for initialization.
116116
*/
117117
function registerLanguageModelTools(context: vscode.ExtensionContext, noConfigDebug: NoConfigDebugRegistration): void {
118118
// Check if Language Model API is available

src/javaServerReadiness.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import * as vscode from "vscode";
5+
import { sendError } from "vscode-extension-telemetry-wrapper";
6+
import { getJavaExtensionAPI } from "./utility";
7+
8+
export type JavaServerReadinessState =
9+
| { status: "initializing" | "ready" | "disposed" }
10+
| { status: "failed"; message: string };
11+
12+
export interface JavaServerReadiness extends vscode.Disposable {
13+
getState(): JavaServerReadinessState;
14+
}
15+
16+
interface JavaServerAPI {
17+
readonly status?: string;
18+
readonly serverReady?: () => Thenable<boolean>;
19+
}
20+
21+
const INITIALIZATION_FAILED = "Java language server initialization failed. "
22+
+ "Check the Java language server logs, resolve the startup problem, and reload VS Code before retrying.";
23+
24+
export function observeJavaServerReadiness(): JavaServerReadiness {
25+
let state: JavaServerReadinessState = { status: "initializing" };
26+
let javaApi: JavaServerAPI | undefined;
27+
let disposed = false;
28+
29+
function reportFailure(message: string): void {
30+
if (disposed) {
31+
return;
32+
}
33+
state = { status: "failed", message };
34+
// Activation errors may contain user paths; only report controlled messages.
35+
sendError({ name: "JavaServerReadinessError", message });
36+
}
37+
38+
async function initialize(): Promise<void> {
39+
const api: JavaServerAPI | undefined = await getJavaExtensionAPI();
40+
if (disposed) {
41+
return;
42+
}
43+
if (!api || typeof api.serverReady !== "function") {
44+
reportFailure("Java language server readiness API is unavailable. "
45+
+ "Update Language Support for Java by Red Hat and reload VS Code before retrying.");
46+
return;
47+
}
48+
javaApi = api;
49+
const ready = await api.serverReady();
50+
if (disposed) {
51+
return;
52+
}
53+
if (!ready) {
54+
reportFailure(INITIALIZATION_FAILED);
55+
return;
56+
}
57+
state = { status: "ready" };
58+
}
59+
60+
void initialize().catch(() => reportFailure(INITIALIZATION_FAILED));
61+
62+
return {
63+
getState() {
64+
// serverReady() is a success signal and need not reject on a server error.
65+
if (!disposed && javaApi?.status === "Error") {
66+
return { status: "failed", message: INITIALIZATION_FAILED };
67+
}
68+
if (!disposed && javaApi?.status === "Stopping") {
69+
return { status: "initializing" };
70+
}
71+
return state;
72+
},
73+
dispose() {
74+
disposed = true;
75+
state = { status: "disposed" };
76+
},
77+
};
78+
}

0 commit comments

Comments
 (0)