Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ The debugger will automatically attach. See [No-Config Debug Documentation](bund

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.

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.

## AI-Assisted Debugging

When using GitHub Copilot Chat, you can now ask AI to help you debug Java applications! The extension provides a Language Model Tool that enables natural language debugging:
Expand Down
4 changes: 4 additions & 0 deletions bundled/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ Make sure the Java project is properly loaded. Check that:

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.

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.

`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.

Ensure:
- Your project compiles successfully
- No other debug session is running
Expand Down
8 changes: 6 additions & 2 deletions bundled/agents/debug.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,19 @@ vscjava.vscode-java-debug/getDebugSessionInfo() // Check again - should now be

### 2.4 Automatic Cleanup on Restart

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

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

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

When `debugJavaApplication` returns timeout or failure, follow this recovery workflow:
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.

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.

When `debugJavaApplication` returns an actual launch timeout or failure, follow this recovery workflow:

**Step 1: Check terminal output for errors**
```
Expand Down
18 changes: 16 additions & 2 deletions bundled/scripts/noConfigScripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@ This feature enables configuration-less debugging for Java applications, similar

## How It Works

When you open a terminal in VS Code with this extension installed, the following environment variables are automatically set:
Once No-Config Debug initialization finishes, newly opened VS Code terminals receive the following environment contributions:

- `VSCODE_JDWP_ADAPTER_ENDPOINTS`: Path to a communication file for port exchange
- `PATH`: Includes the `debugjava` command wrapper

Note: `JAVA_TOOL_OPTIONS` is NOT set globally to avoid affecting other Java tools (javac, maven, gradle). Instead, it's set only when you run the `debugjava` command.

### Startup readiness

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.

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.

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.

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.

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.

On disposal, listeners are released immediately even if initialization is still pending. Already-started filesystem operations or Java extension activation are not forcibly cancelled, but their late completion cannot publish environment updates or register new listeners.

## Disabling No-Config Debug

No-Config Debug is enabled by default. To opt out for all workspaces or just the current workspace, add this to the corresponding VS Code settings:
Expand Down Expand Up @@ -104,7 +118,7 @@ If you see "Address already in use", another Java debug session is running. Term

1. Ensure you're running with `debugjava` command (not plain `java`)
2. Check that the `debugjava` command is available: `which debugjava` (Unix) or `Get-Command debugjava` (PowerShell)
3. Verify the terminal was opened AFTER the extension activated
3. Verify the terminal was opened after No-Config Debug initialization finished; recreate an early terminal if its environment is missing the contributions
4. Check the Debug Console for error messages

### Node.js Not Found
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@
{
"name": "debug_java_application",
"displayName": "Debug Java Application",
"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']).",
"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']).",
"toolReferenceName": "debugJavaApplication",
"tags": [
"java",
Expand Down
4 changes: 3 additions & 1 deletion resources/instruments/javaDebugContext.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ For Java run/launch/debug/inspection requests, prefer the Java debug language mo

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.

Fall back to `run_in_terminal` only when `debug_java_application` returns "Java Language Server not ready" or "project not detected".
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.

Fall back to `run_in_terminal` when `debug_java_application` returns "project not detected".
7 changes: 5 additions & 2 deletions resources/skills/java-launch-troubleshooting/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ These language model tools are contributed by the `Debugger for Java` extension
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)?
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.
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.
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`.
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.
5. **Stop when done.** When the user says "stop", "kill it", or has the answer they need, call `stop_debug_session`.

## Common Failure Modes

| Symptom from `debug_java_application` | Likely cause | Suggested fix |
|---|---|---|
| `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 |
| `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 |
| `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 |
| `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` |
| `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` |
| `Compilation failed` with file:line | Source code has a compile error | Fix the reported error in the source file, do not retry the launch |
Expand All @@ -47,4 +50,4 @@ These language model tools are contributed by the `Debugger for Java` extension

## Fallback

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.
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.
23 changes: 10 additions & 13 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ENABLE_NO_CONFIG_DEBUG, HCR_EVENT, JAVA_LANGID, TELEMETRY_EVENT, USER_N
import { NotificationBar } from "./customWidget";
import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvider";
import { initExpService } from "./experimentationService";
import { registerNoConfigDebug } from "./noConfigDebugInit";
import { NoConfigDebugRegistration, registerNoConfigDebug } from "./noConfigDebugInit";
import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace, NO_BUTTON, YES_BUTTON } from "./hotCodeReplace";
import { JavaDebugAdapterDescriptorFactory } from "./javaDebugAdapterDescriptorFactory";
import { JavaInlineValuesProvider } from "./JavaInlineValueProvider";
Expand All @@ -39,20 +39,18 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {

// Capture once so terminal integration and the AI launch tool both require a reload to change.
const noConfigDebugEnabled = vscode.workspace.getConfiguration().get<boolean>(ENABLE_NO_CONFIG_DEBUG, true);
const noConfigDisposable = await registerNoConfigDebug(
const api = await instrumentOperation("activation", initializeExtension)(context);
const noConfigDebug = registerNoConfigDebug(
context.environmentVariableCollection,
context.extensionPath,
context.storageUri,
noConfigDebugEnabled,
);
if (noConfigDisposable) {
context.subscriptions.push(noConfigDisposable);
}
context.subscriptions.push(noConfigDebug);

// Register Language Model Tools after Java Language Server is ready
registerLanguageModelToolsWhenReady(context, noConfigDebugEnabled);
registerLanguageModelTools(context, noConfigDebug);

return instrumentOperation("activation", initializeExtension)(context);
return api;
}

function initializeExtension(_operationId: string, context: vscode.ExtensionContext): any {
Expand Down Expand Up @@ -113,11 +111,10 @@ export async function deactivate() {
const delay = promisify(setTimeout);

/**
* Register Language Model Tools after Java Language Server is ready.
* The debug tools depend on JDT.LS for compilation, classpath resolution,
* and executing debug server commands.
* Register tools when the Java extension is installed. The launch tool checks
* Java and No-Config readiness at invocation without waiting for initialization.
*/
async function registerLanguageModelToolsWhenReady(context: vscode.ExtensionContext, noConfigDebugEnabled: boolean): Promise<void> {
function registerLanguageModelTools(context: vscode.ExtensionContext, noConfigDebug: NoConfigDebugRegistration): void {
// Check if Language Model API is available
if (!vscode.lm || typeof vscode.lm.registerTool !== 'function') {
return;
Expand All @@ -129,7 +126,7 @@ async function registerLanguageModelToolsWhenReady(context: vscode.ExtensionCont
}

// Register Language Model Tools for AI-assisted debugging
registerLanguageModelTool(context, noConfigDebugEnabled);
registerLanguageModelTool(context, noConfigDebug);
const debugToolsDisposables = registerDebugSessionTools(context);
context.subscriptions.push(...debugToolsDisposables);

Expand Down
78 changes: 78 additions & 0 deletions src/javaServerReadiness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";
import { sendError } from "vscode-extension-telemetry-wrapper";
import { getJavaExtensionAPI } from "./utility";

export type JavaServerReadinessState =
| { status: "initializing" | "ready" | "disposed" }
| { status: "failed"; message: string };

export interface JavaServerReadiness extends vscode.Disposable {
getState(): JavaServerReadinessState;
}

interface JavaServerAPI {
readonly status?: string;
readonly serverReady?: () => Thenable<boolean>;
}

const INITIALIZATION_FAILED = "Java language server initialization failed. "
+ "Check the Java language server logs, resolve the startup problem, and reload VS Code before retrying.";

export function observeJavaServerReadiness(): JavaServerReadiness {
let state: JavaServerReadinessState = { status: "initializing" };
let javaApi: JavaServerAPI | undefined;
let disposed = false;

function reportFailure(message: string): void {
if (disposed) {
return;
}
state = { status: "failed", message };
// Activation errors may contain user paths; only report controlled messages.
sendError({ name: "JavaServerReadinessError", message });
}

async function initialize(): Promise<void> {
const api: JavaServerAPI | undefined = await getJavaExtensionAPI();
if (disposed) {
return;
}
if (!api || typeof api.serverReady !== "function") {
reportFailure("Java language server readiness API is unavailable. "
+ "Update Language Support for Java by Red Hat and reload VS Code before retrying.");
return;
}
javaApi = api;
const ready = await api.serverReady();
if (disposed) {
return;
}
if (!ready) {
reportFailure(INITIALIZATION_FAILED);
return;
}
state = { status: "ready" };
}

void initialize().catch(() => reportFailure(INITIALIZATION_FAILED));

return {
getState() {
// serverReady() is a success signal and need not reject on a server error.
if (!disposed && javaApi?.status === "Error") {
return { status: "failed", message: INITIALIZATION_FAILED };
}
if (!disposed && javaApi?.status === "Stopping") {
return { status: "initializing" };
}
return state;
},
dispose() {
disposed = true;
state = { status: "disposed" };
},
};
}
Loading
Loading