Skip to content

v2.3.10

Pre-release
Pre-release

Choose a tag to compare

@MatthiasHertelArm MatthiasHertelArm released this 07 Sep 12:14

Highlights

  • Motion tools wait for the real stop. continue_execution, step_over, step_into, step_out and pause_execution used to settle on VS Code's active-stack-item change — which also fires when the frame is cleared on resume — and could report a stop with an empty location while the target was running. They now arm a DAP stopped waiter before the request goes out and open the result with the stop reason.
  • One pack root, agreed with the CMSIS Solution extension. %LOCALAPPDATA%\Arm\Packs on Windows; next to the CMSIS Solution extension its own getPackRootPath answer, asked once per window, carried on every target resolution and shared with the SVD lookup — so list_target_docs and lookup_peripheral look in the same directory.
  • Containment. fetch_doc refuses loopback, link-local, private and cloud-metadata addresses and re-checks every redirect hop; get_build_diagnostics { file } reads inside the open workspace only.
  • Multi-window robustness. UTF-8-safe control-channel bodies with size caps, atomic registry and agent-configuration writes (~/.claude.json is re-read before every write), bounded shutdown, serial ports released by the window that owns them, a pyOCD flash that ignores SIGTERM is now killed.
  • Documentation tools. maxPdfMb on read_doc_pages and imports, ambiguous short ids reported instead of guessed, user-document ids carry their scope folder (user/keil/stm32u5xx-dfp/rm0456), a shared timeout fence that never leaves an unhandled rejection.
  • The whole set comes from a codebase review (docs/codebase-review-2026-09.md, phases 0–2 of its plan) with 66 new tests: 491 passing, both transport suites, lint and type-check clean.

[2.3.10] - 2026-09-07

Added

  • Tests for the DNS-rebinding guard (isLoopbackHostHeader / isLoopbackOrigin), the hardware timeout wrapper, and the pdf.js worker's positive timeout, restart, idle retirement and dispose-while-pending paths (through an injectable stand-in worker).
  • The Pack Docs panel can clear its store — in two separate steps, each behind a confirmation. Clear extracted text… removes every document's extracted pages, metadata and search index (*.pages.jsonl, *.meta.json, *.idx.json) across the store and prunes the emptied directories; downloaded PDFs and fetch records stay, so fetched documents remain fetched and every document is extracted and indexed again on its next use. Delete downloaded PDFs… removes what fetch_doc downloaded — the arm/ and web/ trees with their PDFs, fetch records and extraction — so those documents are offered as "not fetched" again. The modal confirmation names the document count, file count and size it is about to remove; the in-memory page, index and chapter caches are dropped with the files. PageStore gains storeUsage(), clearExtracted() and clearDownloads(), tested.

Changed

  • The Pack Docs panel is laid out by what each tab shows. The target picker — a cbuild-run context or pack + device — moved into a header shared by every tab, with the resolution and core under it, since it drives them all. The former Target tab, which stacked the SVD browser above the document list, is split into Documents (every document the tools see for the target, its state and the fetch / index / browse / search actions) and Peripherals (the device SVD, the Arm core peripherals and the NPU as groups, instances and bit views); Store is Page store (what is extracted and indexed on disk, across all targets) and Tools stays. Each tab carries a count and a tooltip, opens with a one-line description of what it shows, and the panel no longer refers to "the Target tab" in the other tabs. A saved tab from the old layout opens as Documents.

Changed

  • User-document ids name their scope folder (user/keil/stm32u5xx-dfp/rm0456, user/devices/stm32u5/errata, user/boards/b-u585i-iot02a/schematic), so the same file name attributed to two scopes no longer collides or swaps ids between calls; a document at the root of the user folder keeps user/<name>. read_doc_pages and the docs filter still accept the bare file name when it is unique. Extracted text and indexes are unaffected. Tested.
  • A short document id that matches several documents is reported as ambiguous by read_doc_pages and fetch_docDocument id 'rm' is ambiguous — it matches … — instead of silently reading pages of whichever the listing yielded first. A unique trailing segment (test-rm for stm32f7xx-dfp/test-rm) and a case difference still resolve. Tested.
  • reset tells the truth about the end state. When the last (or only) reset method is unsupported the result no longer says "trying the next method"; when the reset could not be verified the result says the target is halted, that it was running before when the tool halted it, and that halt: false was not applied — an unverified target is never resumed. ResetOutcome gains resumed; the rendering moved to resetAssist.ts. Tested.
  • get_threads reads top frames for the first 32 threads in batches of four instead of one stackTrace request per RTOS task fired at once — a GDB server serialises DAP requests, so a 60-task build made every request wait on the others and hit the deadline together.
  • cmsis_action's post-connect session check fits its advertised window. The two thread probes after load_and_debug / attach were fixed at t+3 s and t+6 s with 5 s each, up to 16 s past an "opportunistic" 8 s wait; they are now scheduled at 35 % and 75 % of that window. probeSchedule is exported and tested.

Fixed

  • fetch_doc no longer reaches local or private addresses. A pdsc <book url> or a fetch_doc { url } pointing at loopback, link-local, RFC 1918, CGNAT, multicast or cloud-metadata addresses (127.0.0.1, localhost, 169.254.169.254, 10.x, 192.168.x, fe80::, …) is refused before any request is made, and redirects are followed one hop at a time with the same check on every target, so a public URL cannot bounce the download onto the MCP or control port. The error names the workspace docs folder as the way to use such a document. A public name that resolves to a private address is not caught (no DNS lookup). PackDocsHost.allowPrivateHosts lets a loopback fixture through in tests. Tested.
  • packDocs.maxPdfMb applies to read_doc_pages and to imported documents too, not only to search and the index command; an oversize document is reported with the setting to raise instead of being extracted anyway. Tested.
  • A window that opened a serial port through serial_open releases it when the window closes, whether or not it was the router. Serial teardown moved from the MCP server (router window only) to the per-window coordinator, bounded to two seconds so a wedged tty cannot hold deactivate. Tested.
  • Closing a window no longer waits on open MCP or control connections. The HTTP servers close idle sockets at once and destroy the rest after two seconds, so an in-flight tool call or an open notification stream cannot hold the extension host in deactivate; a window that failed to bind the router port releases the partially started server. Tested.
  • serial_close after the adapter was unplugged no longer wedges serial_open with "already open". The controller forgets the port even when closing it fails. Tested.
  • read_peripheral_register honours dapRequestTimeoutMs and the call's timeoutMs instead of a fixed 10 s per DAP request, and the GDB evaluate fallbacks used by read_memory forward the same deadline as the DAP attempt. Tested.
  • An MCP session whose initialize failed mid-way no longer leaks its transport and server objects.
  • Agent configuration files (~/.claude.json, …) are re-read immediately before they are written and the update is retried when another process changed the file in between, so a Claude Code write during setup is no longer lost; an unchanged file is not rewritten at all. src/utils/jsonFileRewrite.ts, tested.
  • Removed an upstream coreclr special case in start_debugging that opened the program file and fired the test-explorer debug command without awaiting it.
  • continue_execution, step_over, step_into, step_out and pause_execution no longer report a stop with an empty location while the target is still running. They settled on VS Code's active-stack-item change, which also fires when the frame is cleared on resume, and the continued event of the request just sent usually arrived after the listener was attached. They now wait for the DAP stopped event — the signal wait_for_stop and reset already use — armed before the request goes out so a stop that lands during the round trip is not missed, and the result opens with the stop reason (Target stopped (reason: breakpoint)). The timeout recovery pauses the same way. IDebuggingExecutor gains armStopWaiter. Tested with a scripted executor.
  • A pyOCD flash run that ignored SIGTERM at the deadline was never killed and flash hung. The SIGKILL escalation checked child.killed, which is true as soon as the SIGTERM was sent; it now checks that the process has not exited. flashWithPyocd takes an injectable spawn and grace period. Tested with a child that ignores SIGTERM.
  • Tool results forwarded between VS Code windows could arrive with in place of emoji or CJK text. The router and each window's control server appended body chunks to a string, so a multibyte UTF-8 sequence split across two chunks was decoded in halves; both now collect the raw bytes and decode once. They also refuse bodies above 1 MiB (requests) or 16 MiB (results) instead of buffering without limit, the MCP endpoint's body limit is an explicit 1 MiB, and a client that aborts mid-request no longer leaves the control server's response open. Tested with a request and a reply split inside a four-byte character.
  • A pdsc containing an out-of-range numeric character reference (&#99999999;) made list_target_docs fail with "Invalid code point". Such references are now left as written, like unknown named entities. Tested.
  • A disk error while fetch_doc was writing a download (full disk, unwritable page store) crashed the extension host or left the call hanging. The write stream's error is observed from the first byte and raced against the back-pressure wait, so it becomes the tool's error and the partial file is removed. Tested with an unwritable .part path.
  • A documentation or build-artefact call that failed after its timeout had already been reported left an unhandled promise rejection in the extension host. The two handlers' identical timeout fences are now one helper (src/core/toolRun.ts) that keeps observing the call and logs its late outcome. Tested.
  • A VS Code window could lose its entry in the multi-window registry while another window was reading it. Registrations were written in place and any file that failed to parse was deleted; they are now written to a temp file and renamed (src/utils/atomicFile.ts), the heartbeat rewrites the window's own copy instead of re-reading the file, and an unparsable file is only removed once it is older than the staleness window. Tested.
  • Two VS Code windows syncing skills at the same time could delete each other's in-progress copy, failing the install in one of them. The stale-staging sweep removed every .<skill>.tmp-<pid> directory; it now skips those whose process is still alive. Tested.
  • Two VS Code windows writing an agent configuration at the same time could clobber each other's temp file, and the Codex config.toml was written in place rather than atomically. Every agent configuration write goes through the same temp-file-plus-rename helper with a per-process unique temp name.
  • get_build_diagnostics { file } reads build logs inside the open workspace only. An absolute or .. path outside every workspace folder is refused, and a file that carries no compiler or cbuild output is reported as "does not look like a build log" instead of being rendered — the tool was an arbitrary-file read for the agent. Tested.
  • A zero or negative pdf.js timeout is decided synchronously instead of on a zero timer. A zero timer still raced the worker's reply: a warm thread answers a small document within one Windows scheduler tick, so the "timed out" rejection the page-store test expects was missing on the windows-11-arm runner. A timeout of zero or less now rejects and terminates the worker right after the request is posted.
  • On Windows the documentation tools looked for packs under %USERPROFILE%\.cache\arm\packs, so list_target_docs reported every pack as "not installed". The CMSIS-Toolbox default pack root is %LOCALAPPDATA%\Arm\Packs on Windows and ~/.cache/arm/packs only on Linux and macOS; defaultPackRoot now follows the platform (falling back to <home>\AppData\Local\Arm\Packs when LOCALAPPDATA is unset), and CMSIS_PACK_ROOT still overrides it. The ${CMSIS_PACK_ROOT} expansion in svdFile paths shares the same resolution instead of its own copy of the Linux path. Tested for all three platforms. Next to the CMSIS Solution extension the tools no longer guess at all: before every target resolution they ask it for its pack root (cmsis-csolution.getPackRootPath, the same CMSIS_PACK_ROOT-or-OS-default the panel builds with) and adopt the answer, so both extensions look in the same directory and a "not installed" note names the one the panel would install into. PackDocsHost gains the optional packRootFromToolchain hook; an absent, slow, empty or failing answer keeps the platform default. The extension is asked once per window (again after an extension or settings change; an unanswered ask is retried after a minute) and the output channel reports which root was adopted. Every consumer — the document list, the SVD lookup, the CMSIS-Core and NPU headers — reads the root the resolution was made under (TargetResolution.packRoot), so the first call in a window no longer looks in the platform default while the answer is still in flight; and lookup_peripheral, read_peripheral_register and diagnose_fault expand ${CMSIS_PACK_ROOT} in svdFile and cbuild-run entries with that same root. Tested.
  • Document indexing failed next to the CMSIS csolution extension: "The Array.prototype contains unexpected enumerable property "groupedBy"". pdf.js refuses to start on a realm whose Array.prototype or Object.prototype has an enumerable property, and VS Code's extension host is one process shared by every extension — the csolution extension assigns Array.prototype.groupedBy, so every PDF was skipped and search_target_docs found nothing indexed. pdf.js now runs on a worker_threads worker of its own (dist/pdfWorker.js, a second esbuild entry point): a fresh realm no other extension can patch. The thread is started on the first document, kept for the next, retired after a minute idle, and terminated when an extraction times out — which now actually stops pdf.js mid-document instead of waiting for the next page. The bundled pdf.worker.min.mjs still ships for the thread's fake worker; itemsToPageText moved to pdfText.ts. Tested with a patched Array.prototype, a timeout followed by a fresh extraction, and a missing file.

Pre-release (odd minor). Full changelog: https://github.com/MatthiasHertelArm/CMSIS-Developer-Assistant/blob/v2.3.10/CHANGELOG.md — previous pre-release: https://github.com/MatthiasHertelArm/CMSIS-Developer-Assistant/releases/tag/v2.3.9 (documentation and build-artefact tools built in) — based on Open-CMSIS-Pack main after PR Open-CMSIS-Pack#30 (2.3.9) merged.