From 9faa316e33a8240c4e2477823efbee5a71f68dc9 Mon Sep 17 00:00:00 2001 From: JF Date: Mon, 24 Aug 2026 20:18:38 -0400 Subject: [PATCH 1/2] docs(#462): reconcile CHANGELOG [Unreleased] with everything merged since v0.24.2 The section was missing ~15 merged changes (doctor, canary, kind smoke, Rust formatter fix, perf batch, hardening batch, attestation, ...) and carried a duplicate ### Fixed block. Rebuilt from git log v0.24.2..HEAD --no-merges: 37 entries under single Added/Changed/Fixed/Security headers, including this cycle's sweep (#463-#471 + #478) with first-time contributors credited. Also adds the reconcile step the issue asked for to docs/release-checklist.md, so the gap cannot silently recur. Deliberately omitted: dependency bumps and pure test-coverage PRs (#415-#418), per the checklist note. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 37 ++++++++++++++++++++++++++++++++----- docs/release-checklist.md | 1 + 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4aa1b4..d48b65fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,24 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **`mcp-debugger doctor` — environment self-check command** — one command renders a per-language table of runtime/backend toolchain state (found/missing, versions, paths) with actionable install hints, so "why doesn't language X work" is a 5-second diagnosis instead of a log spelunk. Adapters own their presentation via a new `describeToolchain()` factory hook; a consolidated diagnostics guide cross-links doctor, logs, `dryRunSpawn`, and `DAP_TRACE` (#423) +- **Adapter availability unified on one shared probe** — the server's language gate, `list_supported_languages`, and doctor all consult the same per-entry toolchain probe (TTL-cached), and the adapter registry surface is fully typed (`IAdapterRegistry`); `tools/list` no longer imports all 9 adapters to answer availability (#435) +- **Scheduled canary: install-and-debug matrix over the published artifacts** — a workflow (also the post-release gate) installs the *published* npm package (default and `--omit=optional` + `CODELLDB_PATH` legs), runs npx, pulls the Docker image, and drives a real breakpoint → variables → continue cycle in mock/python/rust across x64/arm64 Linux, arm64 macOS, and Windows — packaging regressions surface on a schedule instead of in user reports (#425) +- **Weekly kind smoke job for the Kubernetes recipe** — spins up kind, applies the registry-free example manifests, and drives attach + a function breakpoint through the Streamable HTTP sidecar, so the #424 recipe stays verified against current images (#451) +- **The debugging skill and in-band server instructions point at the Kubernetes attach recipe** — remote-attach guidance now lands in the context agents actually read (#452) +- **`get_stack_trace` accepts `threadId`** — inspect a specific thread (ids from `list_threads`); when it reports frames it becomes the anchor for follow-up scopes/locals/evaluate calls. The escape hatch for sessions anchored to a frameless or arbitrary thread (#465) +- **`redefine_classes` reports `anchorResolution`** — statement-anchored breakpoints are re-resolved against the new source after a hot-swap and re-sent to the JVM, with the same `{moved, stale}` shape `restart_debugging` returns (see Fixed, #464) +- **Repeatable memory-footprint benchmark** — `scripts/mem-bench.mjs` + an advisory workflow track RSS across idle/busy scenarios (#395) +- **ROADMAP.md** — the path to 1.0, published (#429) +- **Turnkey Kubernetes debugging recipe** — new [docs/kubernetes.md](docs/kubernetes.md) operational reference (pattern decision table, worked port-forward and ephemeral-sidecar examples, attach path rules, security notes, EKS notes) plus registry-free example manifests under [examples/kubernetes/](examples/kubernetes/) — one self-contained YAML per attach-capable language built from stock public images with the app source in a ConfigMap (initContainer compiles for java/cpp), so `kubectl apply` works identically on kind and managed clusters with no image build or registry — and per-language copy-paste [attach presets](examples/kubernetes/attach-presets.md). Verified end-to-end on kind and Amazon EKS with the published `debugmcp/mcp-debugger:latest` sidecar. Also corrects the JIT-diagnostics tutorial's Part 1 attach call: `localRoot`/`remoteRoot` were silently dropped by the python adapter (no path-mapping lever exists) — breakpoints on attach use debuggee-side paths (#424) +- **CodeLLDB ships as per-platform npm packages (esbuild pattern)** — five new packages `@debugmcp/codelldb-{win32-x64,darwin-x64,darwin-arm64,linux-x64,linux-arm64}` (versioned by the CodeLLDB release, currently 1.11.8, payload staged from the digest-pinned VSIXs) are `optionalDependencies` of `@debugmcp/mcp-debugger`, so npm installs exactly the one matching your os/cpu. Rust and C/C++ debugging now work out of the box on every platform npm serves — previously the CLI tarball bundled linux-x64 only — and the core tarball shrinks from ~54 MB to a few MB. The resolver probes the installed platform package last — after the vendor tree and after `CODELLDB_PATH` — so an explicit `CODELLDB_PATH` still overrides the auto-installed package, and installs with `--omit=optional` keep working via `CODELLDB_PATH` (#383) + ### Changed - **Attach verification window default raised 5s → 20s** — the `attach_to_process` thread-poll deadline only ever bites when the debug adapter is alive but the target is slow to become debuggable (js-debug child-session adoption on a loaded host, a warming JVM); adapter death still fails fast via the proxy-gone latch, and the poll returns the moment threads appear, so healthy attaches never pay for the headroom. A false "attach failed" on a healthy target is strictly worse for an agent than a slower genuine failure. Pass a small `verifyTimeout` for fast failure-by-design probes +- **Ruby rejects logpoints up front** — rdbg does not advertise `supportsLogPoints` and silently converted a logpoint into a *pausing* breakpoint (the inverse of the contract) while every response read as success; `set_breakpoint` with `logMessage` on ruby now fails fast with the same clear error java/dotnet produce. For adapters whose support is genuinely unknown (dynamically loaded), the deferred verdict now actually arrives: the `start_debugging` response warns when live capabilities lack logpoint support and names each downgraded logpoint (#469) +- **Startup performance batch** — SSE/HTTP transport stacks lazy-import in CLI actions, availability probes are metadata-only, orphan reapers are scoped to server commands and share one bounded process scan (with an opt-out), and the DAP trace is opt-in (`DAP_TRACE=1`) with the per-session proxy log honoring the effective log level (#407–#411) +- **`packageManager` pinned to pnpm@10.33.0** — a different pnpm major regenerated `pnpm-lock.yaml` incompatibly (one contributor diff silently dropped the entire security `overrides` block); the pin plus a CONTRIBUTING note make the wrong-pnpm case fail fast, and the workflows read the pin instead of duplicating a version (#478) ### Fixed +- **Java attach is inspectable on the first try** — the JDI bridge never suspends the VM on attach and java's policy lacked the post-attach pause every other attach adapter has, so the session reported `paused` while the JVM kept running: empty stack, "the debugger may not be paused", and no discoverable recovery. Java now pauses the whole VM after attach (the bridge anchors its stopped event to a thread that can actually report frames), the frameless-thread notes name a real recovery (`threadId` targeting or continue+pause), and `get_local_variables` no longer contradicts the session state (#465) +- **`get_local_variables` walks down past an empty runtime top frame** — a pause inside a blocking syscall/sleep put a stdlib frame with no locals at `stackFrames[0]` and the tool returned an empty array even though the user frame's locals were one frame down and *already fetched*; extraction now re-anchors to the first frame that yields locals (zero extra DAP round-trips), reports that frame in `frame`, and a `note` discloses the walk-down. Skipped under an explicit `names:` filter (#468) +- **Statement-anchored breakpoints survive `redefine_classes`** — the hot-swap replant was purely by line, so a line-shifting swap silently rebound a `statement:`-anchored breakpoint to whatever now lives at the old line, reporting `verified: true`; anchors are now re-resolved against the new source (the `restart_debugging` machinery) and re-sent after the redefine so the JDI replant binds the moved lines. The bridge's `replantedBreakpoints` count also no longer drops re-planted function breakpoints (#464) +- **Unbound breakpoints surface where the agent is looking** — a launch that ran to completion with breakpoints that never bound now says so in the `start_debugging` response `warning` (with each breakpoint's stored diagnostic, e.g. CodeLLDB's valid-location path suggestion); and a function-breakpoint name the server *knows* can never bind is corrected instead of stored dead: go's bare `main` auto-qualifies to `main.main` with the rewrite disclosed (`requestedName` + warning) via a new `normalizeFunctionBreakpointName` policy hook (#467) +- **Verified JS breakpoints no longer carry the provisional "unbound" note** — js-debug's pending-target stub stamps `breakpoint.provisionalBreakpoint` (a raw l10n key, untranslated because the adapter spawn has no l10n bundle) and nothing cleared it on bind, so `list_breakpoints` asserted `verified: true` alongside a message meaning "Unbound breakpoint". Messages are now normalized at every stamp site: known keys translate to their English fallbacks, and a provisional note — in raw or translated form — cannot survive verification, including when the bind event carries no message at all. Thanks @MsfPablo for the fix (#471) +- **rdbg launches no longer die on a transient framing hiccup** — the DAP frame decoder discarded everything after a malformed header block, so junk bytes ahead of rdbg's `initialize` response took the response down with them and the launch failed hard at the 30s init deadline blaming adapter startup; the decoder now resyncs at the next header inside the remainder (junk costs one warning, not the session), and a header-decode error while requests are pending is logged at error level naming the request seq(s) (#470) +- **`dist-tarball/mcp-debugger-latest.tgz` is npm-installable again** — `build-npx-tarball.cjs` packed without the prepare-pack rewrite, so the tarball shipped pnpm-only `workspace:*` specifiers (`EUNSUPPORTEDPROTOCOL` on install) and the npx test arm silently resolved CodeLLDB from the repo's own vendor tree; the pack is now wrapped in prepare/restore (restore survives a failed pack) and the build fails if the packed manifest still contains `workspace:`. Thanks @abhijeetnardele24-hash for the fix (#463) +- **Docker Rust type summaries render again** — the image has no rustc, so CodeLLDB could not locate the toolchain's LLDB formatter scripts and Vec/String/HashMap printed as opaque pointers; the image now vendors the formatter scripts and sets `CODELLDB_RUST_SYSROOT` (injected as `_adapterSettings.scriptConfig.lang.rust.sysroot`), with an honest degradation warning on hosts where the sysroot genuinely cannot be found (#441) - **Python attach honors `adapterConfig.pathMappings` — and dropped attach keys now warn instead of vanishing** — the python attach transform was a closed allowlist, so every adapterConfig extra (including debugpy's native `pathMappings: [{localRoot, remoteRoot}]`) was silently discarded and breakpoints at local-checkout paths could never bind against a remote debugpy (the #424 Kubernetes work documented this as a hard limitation). The transform is now a deny-list (the cpp pattern): `pathMappings` and any other debugpy attach knob pass through to the attach request, while the keys debugpy rejects alongside `connect.*` (top-level `host`/`port`), launch-template pollution (`console`), and the ptvsd-era `localRoot`/`remoteRoot` sugar stay stripped. The generic footgun is closed too: caller-provided `adapterConfig` keys that any adapter's attach transform fails to carry into the DAP attach request are named in a top-level `warning` on the `attach_to_process` response — unknown attach keys now either work or warn (#450) - **Logpoints (and any never-paused launch) no longer read as unbound after firing** — `list_breakpoints` used to report `verified: false` with no `adapterId` forever for a launch that ran to completion without pausing, because the store's only reconciliation was a post-launch re-sync gated on the session still being live — exactly the gate a logpoint-only short program has already passed by termination. The signal was inverted: the run that worked looked like the one that failed, in the one workflow (no pause, just logging) where `verified` is the agent's only binding feedback. The proxy worker now forwards its initial `setBreakpoints` results to the parent as a `breakpoints_synced` status (the #302 `function_breakpoints_synced` pattern), matched to the store by an echoed breakpoint id — immune to path canonicalization, stamped before the program can exit. The live re-sync stays as a belt-and-braces pass (#439) - **`get_local_variables` truncation summary describes the returned payload, not discarded scopes** — the multi-scope fan-out counted values cut while fetching scopes the adapter policy then threw away (Global/Closure), so a response with 2 complete variables could claim "78 value(s) cut … flagged truncated:true" — training agents to distrust values that were fine and to burn turns re-fetching data that was never truncated. Per-scope truncation is now attributed to the scope(s) whose variables actually reached the caller (genuine cuts in the returned scope are still counted); `scopesSkipped` keeps reporting fan-out work not done. The notice's `Pass names:` example is also derived from the actually-returned variables (truncated ones first) instead of a hardcoded `["a","b"]` that read as leaked state from another session (#438) - **`expectedContent` no longer warns when the line merely carries a trailing comment** — supplying the entire code of a line whose source ends in a `//` or `#` comment used to trip the "matched as a substring … not the whole line" advisory, contradicting the documented contract that trailing comments are ignored and training callers to distrust the channel meant to catch real off-by-one errors. A new `comment-stripped-exact` match tier (tried before the generic substring pass, only when the expectation itself carries no comment marker — so lines differing inside string literals like `"http://a"` vs `"http://b"` still warn) classifies the whole-code match as exact-equivalent, with no warning (#440) - **A paused session can no longer answer `get_stack_trace` with a silent empty success** — some adapters report a stop before the stack is materialized (netcoredbg right after the post-attach pause — the milder sibling of #353's `0x80131302`), and the tracked thread can be a frameless runtime thread. The agent-facing stack path now retries an empty-but-successful stackTrace within a bounded window (~3s, exits on first frame), falls back to scanning the other stopped threads and adopts the first one with frames (annotated via a `note` in the response, so scopes/evaluate anchor correctly), and — if everything stays frameless — returns the empty result with a `note` saying what to try instead. Not-paused and no-known-thread empty results now carry an explanatory `note` too -### Added -- **Turnkey Kubernetes debugging recipe** — new [docs/kubernetes.md](docs/kubernetes.md) operational reference (pattern decision table, worked port-forward and ephemeral-sidecar examples, attach path rules, security notes, EKS notes) plus registry-free example manifests under [examples/kubernetes/](examples/kubernetes/) — one self-contained YAML per attach-capable language built from stock public images with the app source in a ConfigMap (initContainer compiles for java/cpp), so `kubectl apply` works identically on kind and managed clusters with no image build or registry — and per-language copy-paste [attach presets](examples/kubernetes/attach-presets.md). Verified end-to-end on kind and Amazon EKS with the published `debugmcp/mcp-debugger:latest` sidecar. Also corrects the JIT-diagnostics tutorial's Part 1 attach call: `localRoot`/`remoteRoot` were silently dropped by the python adapter (no path-mapping lever exists) — breakpoints on attach use debuggee-side paths (#424) -- **CodeLLDB ships as per-platform npm packages (esbuild pattern)** — five new packages `@debugmcp/codelldb-{win32-x64,darwin-x64,darwin-arm64,linux-x64,linux-arm64}` (versioned by the CodeLLDB release, currently 1.11.8, payload staged from the digest-pinned VSIXs) are `optionalDependencies` of `@debugmcp/mcp-debugger`, so npm installs exactly the one matching your os/cpu. Rust and C/C++ debugging now work out of the box on every platform npm serves — previously the CLI tarball bundled linux-x64 only — and the core tarball shrinks from ~54 MB to a few MB. The resolver probes the installed platform package last — after the vendor tree and after `CODELLDB_PATH` — so an explicit `CODELLDB_PATH` still overrides the auto-installed package, and installs with `--omit=optional` keep working via `CODELLDB_PATH` (#383) - -### Fixed - **Startup orphan reapers no longer spike memory on hosts with many processes** — both reapers walked `/proc` with an unbounded `Promise.all` over every pid, so a host with ~1600 processes held that many pending promises and read buffers at once while the libuv threadpool retired four at a time. The resulting peak survived for the whole process lifetime, because V8 does not hand grown arenas back to the OS. Reads are now capped at 32 in flight: measured on a 1600-process Linux host the two reapers add 32 MB instead of 203 MB and finish faster (254 ms vs 377 ms), which takes a freshly started stdio server from 287 MB to 190 MB RSS - **Docker image builds vendor only the image's own platform** — the builder stage now sets `CODELLDB_VENDOR_ALL=false`, so the prebuild vendor step reuses the digest-verified engine the Dockerfile already fetched instead of re-downloading ~450 MB of win32/darwin CodeLLDB payloads the Linux image never uses (the `.dockerignore` excludes them from the context, so every fresh build paid that download and could fail on any network blip) - **vendor-codelldb.js can no longer die silently with exit 0** — the script-level root cause behind #389 (the Docker workaround shipped in v0.24.2 stands): a stalled extract-zip promise drained the event loop and Node exited 0 with no failure output. Extraction now runs under a watchdog (default 120 s, `CODELLDB_EXTRACT_TIMEOUT_MS`) whose pending timer keeps the event loop alive and converts a stall into a normal retry/failure, and a premature-exit guard forces exit code 1 with a requested/completed/unresolved-platforms diagnostic if the process would otherwise exit 0 before vendoring finished (#389) +- **Stranded js-debug instances are reaped** — vsDebugServer processes are tagged at spawn via an argv contract and swept on startup when their owning server is gone, closing the orphan gap a SIGKILLed server left behind (#431); a dangling-rejection race in the proxy (the seed-1038859894 flake) is also fixed (#420) +- **HTTP mode: per-session loggers detach from the shared file transport on stop()** — a stopped session could previously close the transport out from under concurrent sessions (#412) +- **Bounded accumulation hardening** — the DAP frame decoder caps frame size and header search (linear accumulation, `DAP_MAX_FRAME_BYTES`), and the remaining unbounded proxy-worker collections are bounded (#413, #414) +- **Thread-anchor guard on `threads` responses + honest `tool:response` logging** — a threads response can no longer clobber the current-thread anchor mid-pause, and the tool-response log's `success` now mirrors the payload's own flag (#407) +- **CI: the Test Summary gate fails when any needed job did not succeed** (previously a skipped job could pass the summary) (#427); vendor outputs no longer embed wall-clock timestamps, keeping shipped bundles reproducible (#428) + +### Security +- **Release attestation extended to the Docker image and PyPI launcher** — the docker image publishes a signed provenance attestation and the PyPI launcher ships PEP 740 attestations, alongside the existing npm sigstore provenance and SBOMs (#430) ## [0.24.2] - 2026-08-19 diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 1fef2b9f..f6f1be48 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -17,6 +17,7 @@ Pre-release validation for mcp-debugger. Run `npm run release:dry-run` to automa - [ ] `release.yml` runs `scripts/resolve-workspace-deps.cjs` before publishing (published manifests must not contain `workspace:*`) ### Manual +- [ ] **CHANGELOG reconciled against git history** — run `git log --oneline v..HEAD --no-merges` and confirm every user-visible merge appears under `[Unreleased]` (dependency bumps and pure test/CI-internal changes may be omitted deliberately), and that the section has no duplicate `### Fixed`/`### Added` headers. The `[Unreleased]` block accretes over weeks of merges — this catch-up is where gaps hide (#462) - [ ] **npm trusted publishing configured** — every *previously published* `@debugmcp/*` package must have a trusted publisher at npmjs.com → package Settings → Trusted Publisher (GitHub Actions; org/user: `debugmcp`, repo: `mcp-debugger`, workflow: `release.yml`, environment: blank). These packages publish token-free via OIDC; a publish without this config fails (404/permission error) — configure, then re-run via workflow_dispatch. - [ ] **First-time packages** — any package that has never been on npm publishes via the `NPM_TOKEN` step in `release.yml` this once. After the release: configure its trusted publisher, then move it from the token step into the OIDC step. When no first-publishes remain, delete the token step and the `NPM_TOKEN` secret. (The five `@debugmcp/codelldb-` packages have their own token step and follow the same dance after their first release.) - [ ] **Docker Hub credentials** — `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets are current From 61e0631a3885510a97b8384ca8d1146c57741f2a Mon Sep 17 00:00:00 2001 From: JF Date: Tue, 25 Aug 2026 11:28:35 -0400 Subject: [PATCH 2/2] docs(#462): changelog entry for #472 dev-proxy timeout fix; align RedefineTargetV2 line layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The V2 hot-swap example had drifted 4 lines from RedefineTarget.java's layout (class at line 12 vs 8), which silently rebinds line-number breakpoints on redefine — the header now states the layout invariant and the line annotations match the V1 targets (11/19/22). Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 1 + examples/java/RedefineTargetV2.java | 21 +++++++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d48b65fa..7f66bb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Unbound breakpoints surface where the agent is looking** — a launch that ran to completion with breakpoints that never bound now says so in the `start_debugging` response `warning` (with each breakpoint's stored diagnostic, e.g. CodeLLDB's valid-location path suggestion); and a function-breakpoint name the server *knows* can never bind is corrected instead of stored dead: go's bare `main` auto-qualifies to `main.main` with the rewrite disclosed (`requestedName` + warning) via a new `normalizeFunctionBreakpointName` policy hook (#467) - **Verified JS breakpoints no longer carry the provisional "unbound" note** — js-debug's pending-target stub stamps `breakpoint.provisionalBreakpoint` (a raw l10n key, untranslated because the adapter spawn has no l10n bundle) and nothing cleared it on bind, so `list_breakpoints` asserted `verified: true` alongside a message meaning "Unbound breakpoint". Messages are now normalized at every stamp site: known keys translate to their English fallbacks, and a provisional note — in raw or translated form — cannot survive verification, including when the bind event carries no message at all. Thanks @MsfPablo for the fix (#471) - **rdbg launches no longer die on a transient framing hiccup** — the DAP frame decoder discarded everything after a malformed header block, so junk bytes ahead of rdbg's `initialize` response took the response down with them and the launch failed hard at the 30s init deadline blaming adapter startup; the decoder now resyncs at the next header inside the remainder (junk costs one warning, not the session), and a header-decode error while requests are pending is logged at error level naming the request seq(s) (#470) +- **dev-proxy: a slow build is no longer misreported as a build failure** — `dev_rebuild_and_restart`'s hardcoded 120 s build timeout made cold-cache docker rebuilds report `Build failed:` (with an arbitrary mid-build log tail) even though the build itself succeeded; the timeout is now configurable via `DEV_PROXY_BUILD_TIMEOUT_MS` and a timeout kill is reported as a timeout — "the build may still have succeeded" — instead of a failure. Thanks @abhijeetnardele24-hash for the fix (#472) - **`dist-tarball/mcp-debugger-latest.tgz` is npm-installable again** — `build-npx-tarball.cjs` packed without the prepare-pack rewrite, so the tarball shipped pnpm-only `workspace:*` specifiers (`EUNSUPPORTEDPROTOCOL` on install) and the npx test arm silently resolved CodeLLDB from the repo's own vendor tree; the pack is now wrapped in prepare/restore (restore survives a failed pack) and the build fails if the packed manifest still contains `workspace:`. Thanks @abhijeetnardele24-hash for the fix (#463) - **Docker Rust type summaries render again** — the image has no rustc, so CodeLLDB could not locate the toolchain's LLDB formatter scripts and Vec/String/HashMap printed as opaque pointers; the image now vendors the formatter scripts and sets `CODELLDB_RUST_SYSROOT` (injected as `_adapterSettings.scriptConfig.lang.rust.sysroot`), with an honest degradation warning on hosts where the sysroot genuinely cannot be found (#441) - **Python attach honors `adapterConfig.pathMappings` — and dropped attach keys now warn instead of vanishing** — the python attach transform was a closed allowlist, so every adapterConfig extra (including debugpy's native `pathMappings: [{localRoot, remoteRoot}]`) was silently discarded and breakpoints at local-checkout paths could never bind against a remote debugpy (the #424 Kubernetes work documented this as a hard limitation). The transform is now a deny-list (the cpp pattern): `pathMappings` and any other debugpy attach knob pass through to the attach request, while the keys debugpy rejects alongside `connect.*` (top-level `host`/`port`), launch-template pollution (`console`), and the ptvsd-era `localRoot`/`remoteRoot` sugar stay stripped. The generic footgun is closed too: caller-provided `adapterConfig` keys that any adapter's attach transform fails to carry into the DAP attach request are named in a top-level `warning` on the `attach_to_process` response — unknown attach keys now either work or warn (#450) diff --git a/examples/java/RedefineTargetV2.java b/examples/java/RedefineTargetV2.java index 885a2350..3c7bd80b 100644 --- a/examples/java/RedefineTargetV2.java +++ b/examples/java/RedefineTargetV2.java @@ -1,28 +1,25 @@ /** - * Modified version of RedefineTarget for hot-reload testing. + * Hot-swap counterpart of RedefineTarget.java — getValue() returns 99, not 42. * - * This file is compiled separately and its .class file replaces the original - * RedefineTarget.class via redefine_classes. The only change is getValue() - * returns 99 instead of 42. - * - * IMPORTANT: The class name must remain "RedefineTarget" (not "RedefineTargetV2") - * so the bytecode matches the loaded class. This file is named V2 only for - * organizational purposes — it is compiled as RedefineTarget.class. + * LINE LAYOUT MUST STAY IDENTICAL TO RedefineTarget.java: a redefine re-resolves + * breakpoints by line number against the new line table, so drift silently rebinds + * them. Class name stays "RedefineTarget"; "V2" is filename-only — stage before javac. */ public class RedefineTarget { static int getValue() { - return 99; // changed from 42 to 99 + return 99; // line 11 — hot-swapped counterpart of RedefineTarget's "return 42" } public static void main(String[] args) throws Exception { System.out.println("RedefineTarget starting..."); - Thread.sleep(2000); + Thread.sleep(2000); // wait for breakpoint setup int val1 = getValue(); - System.out.println("val1 = " + val1); + System.out.println("val1 = " + val1); // line 19 — first breakpoint target (val1 already assigned when paused here) - int val2 = getValue(); + // After hot-reload, getValue() should return 99 + int val2 = getValue(); // line 22 — second breakpoint target System.out.println("val2 = " + val2); System.out.println("RedefineTarget done.");