Skip to content

fix: answers that mislead at a stop or a failure (#1135, #1140, #1141, #1142, #1139) - #1153

Open
debugmcpdev wants to merge 10 commits into
mainfrom
fix/answers-at-stops-and-failures
Open

debugmcpdev wants to merge 10 commits into
mainfrom
fix/answers-at-stops-and-failures

Conversation

@debugmcpdev

@debugmcpdev debugmcpdev commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Cluster 2 of the bugs gating 0.27.0: five answers that are right about the debugger's state and wrong about what the caller should conclude from it.

Closes #1135, closes #1140, closes #1141, closes #1142, closes #1139.

One commit per issue, then five commits from the reviews (three fixes, the tests and doc corrections they asked for, and one doc limit). Every row below was reproduced before its fix and run again after: on main (0f1eb6ac) through the dev proxy, except the two #1139 rows the reviews found (a reset, and a hang-up after initialize), which were measured on this branch's own build with a scripted listener.

What was wrong, and what changes

#1141 — get_stack_trace pointed at itself, and an ended session was explained by one tool of five

The includeInternals hint. get_stack_trace adds "No program frame is active…" only when it hid nothing, and that sentence ended "get_stack_trace with includeInternals: true lists the frames" — said to a caller who had passed the flag and was looking at every frame.

  • ErrorMessages.stopWithoutProgramFrame now takes whether the thread has frames the answer did not show. get_stack_trace never says the clause. The pause and step answers say it when the thread has more than the one frame they show.

An ended session. get_local_variables explained it; get_stack_trace, get_scopes, get_variables and evaluate_expression answered the bare Session is terminated: <id>. The one sentence that existed was also wrong for two kinds of session: after detach_from_process on a Python attach it said "The program has terminated… Use restart_debugging" while the process was still running and restart_debugging refused it (measured).

  • One helper (handlers/shared.ts endedSessionExtras) adds message, worded by the three cases restart_debugging itself tells apart: a child names its parent, an attach session is attached again, a launch is restarted.
  • error is unchanged everywhere.

#1135 — get_local_variables answered a runtime frame's variables as plain locals

extractLocalsFromResolved never read the resolver's noProgramFrame verdict.

  • get_local_variables now carries noProgramFrame: true and the sentence the pause or step answer gave for that stop ("No program frame is active…"), plus "The variables shown belong to '', not to your program."
  • evaluate_expression without frameId had the same gap and gets the same flag, ending "The expression was evaluated in that frame." An explicit frameId adds nothing.
  • The variables are still returned: they are what the adapter reported.
Row from the issue Before After
JavaScript, pause on pause_test.js now = 4210, no flag, no note flag and note
C/C++ on Windows, step_out of main [return value] = 0 and five CRT locals, no flag flag and note
C/C++ in Docker, step_out of main [return value] = 0 from ___lldb_unnamed_symbol_2a580, no flag (the issue's report; not re-run by me on main) flag and note, same variable
Rust, step_out of main no flag unchanged

The Rust row is not this bug: its landing frame (core::ops::function::FnOnce::call_once) is not classified as runtime, so get_stack_trace has no verdict there either. That is #1131; I left a comment there.

#1140 — changedVariables invented a value for a variable that was not there

A name absent from the diff base was written as a change from the string "<undefined>". The shape below is the one the maintainer chose during planning (one map, explicit kinds, gone added now):

"changedVariables": {
  "x":   { "before": "1", "after": "2" },
  "sum": { "after": "30", "new": true },
  "i":   { "before": "3", "gone": true }
}
  • An empty {} still means nothing visible changed.
  • A flag is set only when it is known. The locals a snapshot shows are capped at 20, so the diff base now records whether its list was complete. new needs a complete previous list; without one the entry is { "after": … } alone. gone needs a complete current list; without one the variable is not listed, as before.
  • The adapters' return-value entries stay in the diff, as new.

#1142 — the statement hint could not be followed

An expectedContent mismatch ended "Or address by content: set_breakpoint {statement: ""}" whether or not the text was in the file.

  • The sentence is now resolveStatement's own answer for that text: on one line it names the line; on several it adds nearLine and lists them.
  • Where the resolver would not find the text, no statement is offered. The message names the lines the same expectedContent check passes on ("That expectedContent matches line 3."), or says it matches no line of the file.
  • Both of those claims come from the check's own function (matchLineContent, which assertLineContent now reads too). The resolver ignores comment lines and whatever follows a // or #; expectedContent does not. The first version of this fix said "matches no line of the file" for the resolver's reasons, so text sitting in a trailing comment one line away was called absent. The review caught it.
  • Text spanning lines gets no such sentence.

#1139 — a direct-connect attach described an adapter that never existed

A Python or Ruby attach starts nothing; the worker connects to the caller's host and port. Every failure was worded for a spawned adapter, or not worded at all:

Target Before After
Silent listener (30 s) "…the adapter started and accepted the connection. Retrying usually succeeds…" "127.0.0.1:47811 accepted the connection but never answered the DAP "initialize" request. Nothing was started for this attach: the server connected to what is already listening on that port, so check that it is a debugpy listener (python -m debugpy --listen 127.0.0.1:47811 …) and not another service. Retrying the same port stalls the same way."
Real HTTP port (python -m http.server, about 1 s) "Critical initialization error: DAP client disconnected [Executable: python | adapter PID=none exitCode=n/a]" "Critical initialization error: 127.0.0.1:47812 never answered the DAP "initialize" request (DAP client disconnected). Nothing was started for this attach: …"
Port that resets the connection (about 1 s) "DAP client error: read ECONNRESET" "Critical initialization error: 127.0.0.1:56811 never answered the DAP "initialize" request (read ECONNRESET). Nothing was started for this attach: …"
Debug server that answers initialize, then hangs up (about 1 s) Python: "Proxy exited during initialization. Code: null, Signal: undefined" and ten lines of worker stderr. During configuration: "Error in DAP sequence: DAP client disconnected" "Critical initialization error: DAP client disconnected [direct connect to 127.0.0.1:59591; no adapter process was started]"
Nothing listening (12.5 s) "…[Executable: python | adapter PID=none exitCode=n/a]" "…[direct connect to 127.0.0.1:47819; no adapter process was started]"
  • The worker sends a direct_connect status with what it dialed; the policy supplies what should be listening there. The answer for a 30 s stall carries it as data.initProgress.directConnect; the answers that fail sooner name the endpoint in the message only.
  • A connection that dies mid-handshake reaches the worker as a socket event before the pending request fails: a reset as error, a hang-up as close. The parent fails the attach on the first thing it is sent, and for a close that used to be the connection-closed status. During a direct-connect handshake the first socket event now sends the endpoint report, once (reportDirectConnectDrop).
  • The two reviews found the reset and the hang-up-after-initialize paths. The first version of the fix covered only a hang-up before initialize was answered.
  • The wording is keyed on what is pending: once initialize was answered the endpoint is a debug server, and the port is no longer blamed.
  • The spawned-adapter sentences are byte for byte what they were.

Behaviour a client can observe

  • Changed shape: a new variable in snapshot.changedVariables no longer has before; it has new: true. A variable that left scope is now listed, with gone: true.
  • New fields: noProgramFrame on get_local_variables and evaluate_expression; message on four more tools for an ended session; data.initProgress.directConnect on a direct-connect attach that stalls for the 30 s.
  • Changed text: the notes and messages quoted above. No tool is added or removed (29), and no field is removed.

Evidence

Each fix's unit tests were run failing before the fix. The tests that pin behaviour a review found unpinned passed when written, so each was checked against the reviewer's surviving mutant, which it now kills.

Check Result
Unit suite 424 files, 9,077 tests pass
Integration project 42 files, 194 tests pass (the stress file is skipped by design)
New real-adapter suites (js-debug, debugpy, a real server) 4 files, 7 tests pass
typecheck:all, lint, check-docs, changelog:check pass
Pre-push hook (lint, typecheck, build, unit, integration) on 9e15d749 pass
Live, every row above: through the dev proxy, and for the two review-found #1139 rows with a scripted listener against the built server as quoted

The four real-adapter suites were written after their fixes, so their "before" is the live answers on main quoted above, not a failing test run. The reset and hang-up-after-initialize cases of #1139 are the exception: their tests failed first (for the hang-up, the real-server test too), and their "before" was measured on this branch's own build.

Review

Three independent reviews, each by a fresh agent with a shell. Each ran the built server against its own listeners, ran a mutation pass, and restored every edit.

1. The five issue commits. No Critical findings; four Important, all fixed:

2. The two fix commits above. No Critical findings; one Important, fixed:

3. The last fix commit (6fa2651c). No Critical findings and no regression. One Important, not fixed here:

  • A scripted listener that answers initialize, then sends a DAP terminated event and closes, is still answered "Proxy exited during initialization. Code: 0…" with worker stderr. The worker is already shutting down when the socket closes, so it has no report to send first. Re-run by me; not seen from a real debugpy or rdbg.
  • I stopped rather than change behaviour a fourth time. The cause is one level up: the parent words a failed start from whichever worker message arrives first. That is direct-connect attach: a debug server that ends the session mid-handshake still answers 'Proxy exited during initialization', and a refused configuration request names no endpoint (follow-up to #1139) #1152 (filed), with a direction: the parent already holds the endpoint and can word its own exit path. The changelog and troubleshooting guide state the limit (9e15d749).
  • Mutation pass: 23 mutants, 19 killed. Of the 4 survivors, 2 are killed by a sibling test file, 1 is equivalent, and 1 could not be made visible to a caller.
  • One change for every adapter: after an init failure has been reported, the configuration phase no longer sends a second "Error in DAP sequence: DAP client disconnected" from its own teardown. By the reviewer's reading of the code the parent never read that second message; that was not run end to end with a spawned adapter.

Seen outside the branch by the first reviewer, both reproduced by me, both for cluster 3:

Calls I made

Known limits

🤖 Generated with Claude Code

cynarlab and others added 10 commits November 13, 2026 10:11
…ession is explained by kind (#1141)

Two guidance gaps in the inspection tools, both reproduced on main.

1. The includeInternals hint was circular. get_stack_trace adds the "No
   program frame is active" sentence only when it hid nothing, and that
   sentence ended "get_stack_trace with includeInternals: true lists the
   frames" - so a caller who had passed the flag, and was looking at every
   frame, was told to pass it. ErrorMessages.stopWithoutProgramFrame now
   takes whether the thread has frames the answer did not show. The stop
   answers (stop-location.ts) say so when the thread has more than the one
   frame they show; get_stack_trace never does. A one-frame stack loses the
   clause everywhere: the sentence already names its only frame.

2. An ended session was explained by one tool of five. get_local_variables
   added "The program has terminated ... Use restart_debugging"; get_stack_trace,
   get_scopes, get_variables and evaluate_expression answered the bare
   "Session is terminated: <id>". The sentence was also wrong for two kinds
   of session: after detach_from_process on a Python attach it said the
   program had terminated (it was still running) and offered a restart that
   restart_debugging then refused. One helper (handlers/shared.ts
   endedSessionExtras) now words the message for what the session is, by the
   same three cases restart_debugging tells apart: a child names its parent,
   an attach session is attached again, a launch is restarted. `error` is
   unchanged everywhere; only `message` is added.

Tests were written first and seen failing: the sentence builder, the stop
landing for a one-frame and a four-frame runtime stack, get_stack_trace with
and without includeInternals, and each of the five tools on each kind of
ended session.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…rom is not program code (#1135)

On a stop where no frame of the stack is program code, get_stack_trace and
the pause/step answers carried the resolver's verdict (noProgramFrame, "No
program frame is active..."), and get_local_variables did not: it returned
the runtime frame's variables as plain locals. Reproduced on main with a
pause on examples/javascript/pause_test.js (`now = 4210` from
process.processTimers) and a step_out of main in hello_world.cpp
(`[return value] = 0` and five CRT locals from __tmainCRTStartup).
evaluate_expression without a frameId had the same gap.

Root cause: extractLocalsFromResolved never read StackTraceResult.noProgramFrame,
and the evaluator's anchor fields had no place for it.

- LocalVariablesResult carries the verdict (thread, frame count, top frame)
  when the resolver set it; the handler answers noProgramFrame: true and the
  sentence the stack gives, plus "The variables shown belong to '<frame>',
  not to your program." The clause names the frame the variables came from
  (not the stack's top, after a walk-down) and is left out for an empty list.
- The variables stay. They are what the adapter reported, and the return
  value of main is worth having.
- evaluate_expression without a frameId gets the flag and the sentence on
  anchorNote, ending "The expression was evaluated in that frame." An
  explicit frameId adds nothing: the caller chose the frame.
- The pointer at get_stack_trace follows the rule of the previous commit:
  only when the thread has more frames than the one named.

Not covered: a Rust step_out of main. Its landing (FnOnce::call_once) is not
classified as runtime, so the resolver sets no verdict there; that is #1131.

Tests written first and seen failing: the data layer on one-frame, two-frame
and program-reaching stacks; the handler (flag, clause, walk-down, empty
list, note joining); the evaluator. New real-adapter suite
javascript-no-program-frame.test.ts drives a real server and js-debug: the
three tools agree on the verdict, get_stack_trace with includeInternals: true
never mentions includeInternals, and an ended session is explained by all
five inspection tools.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…g a value (#1140)

A variable absent from the diff base was written as a change from the
string "<undefined>": a value-shaped placeholder in JavaScript's vocabulary.
Reproduced on main: Python `b = 2` stepped over gave
{"b": {"before": "<undefined>", "after": "2"}}, js-debug's return-value
entry gave {"Return value": {"before": "<undefined>", "after": "undefined"}}.
A variable that left scope dropped out silently, so "unchanged" and "gone"
read the same.

The shape, decided with JF on 2026-11-13 (one map, explicit kinds, and gone
added now so the diff's vocabulary is settled before the response-shape
freeze):

  { "before": "1", "after": "2" }      a changed value
  { "after": "30", "new": true }       a variable that appeared
  { "before": "3", "gone": true }      a variable that left scope

An empty {} keeps its meaning: nothing visible changed.

A flag is set only when it is known. The locals a snapshot shows are capped
(20), so a name missing from a list that was cut may only not have fitted:

- the diff base (ManagedSession.snapshotDiffBase) records `complete`: no
  variable omitted, no scope unread. The truncation figures are computed
  before the diff now;
- `new` needs the BASE to have been complete. Otherwise the entry is
  { after } alone;
- `gone` needs the CURRENT list to be complete. Otherwise the variable is
  not listed, as before. Whether the base was cut does not matter here: a
  variable that was shown and is absent from a complete list has left scope.
  (The plan said "both lists complete"; this is the same rule without the
  half that withheld a known fact.)

The adapters' return-value entries stay in the diff, as new. Neither adapter
marks them except by name, and measured they do not flicker: debugpy keeps
"(return) add" in the frame once it appears; js-debug shows "Return value"
at a function's return point, after which the frame changes and the diff
rebases.

Tests written first and seen failing (stop-snapshot.test.ts: 12 red): the
three kinds together, a cut base, a cut current list, an unread scope on
either side, upstream omissions, a merely shortened value, completeness
carried from one diff to the next, the literal text "<undefined>" as a
value, exotic names both ways, the empty read. Through a real step answer
in session-manager-snapshot.test.ts. Real adapters: debugpy (`new`) and
js-debug leaving a block (`gone`), in two new integration suites.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… the resolver would find (#1142)

assertLineContent appended "Or address by content: set_breakpoint
{statement: "<expected>"}" to every mismatch without asking whether that
text resolves. Reproduced on main: line 11 of simple_test.py with
expectedContent "THIS TEXT IS WRONG" earned the hint, and following it
answered `statement "THIS TEXT IS WRONG" not found`.

The sentence is now built from resolveStatement(lines, expected, path, line)
- the function set_breakpoint {statement} itself runs - so the hint and the
resolver cannot disagree:

- one match: "...{statement: "a, b = b, a"} - that text is on line 11."
- several: nearLine (the line asked for) is added and the lines are listed,
  with the one it picks; more than five are summarised.
- none: no statement hint; "That text matches no line of the file, so it
  cannot be addressed by content (statement) either."
- a comment, a blank or text spanning lines: nothing. The resolver would
  refuse it for what it is.

A matched line is never the line that just failed: the resolver's substring
pass is a subset of what assertLineContent accepts for that line.

Tests written first and seen failing (8 red): each of the four outcomes, a
comment-only line never named, the cap on a long list, and a property over
nine lines and nine expectations - every hint emitted, fed back to
resolveStatement, lands on the line it names, and where no hint is given the
resolver finds nothing either. At the tool: an off-by-one refusal names the
line and the same text as `statement` then binds there.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…n adapter that never existed (#1139)

A Python or Ruby attach starts nothing: the worker connects straight to the
host and port the caller gave (the policies' `mode: 'connect'`). Every
failure message was written for a spawned adapter. Reproduced on main:

- a listener that accepts and stays silent (the wrong port) waited 30 s and
  answered "...an adapter-side protocol stall, not a missing install - the
  adapter started and accepted the connection. Retrying usually succeeds";
- a real HTTP port (`python -m http.server`, the issue's own example) hangs
  up on the DAP frame, a different path: "Critical initialization error:
  DAP client disconnected [Executable: python | adapter PID=none
  exitCode=n/a]".

Root cause: ProxyInitProgress had no fact for connect mode, and the worker's
init-failure report described an executable and an adapter process whatever
the mode.

- The worker says what it dialed: in its connect branch it sends a
  `direct_connect` status (host, port, and the policy's phrase for what
  should be listening there - a new optional `listener` on the connect
  spawn config, supplied by the Python and Ruby policies).
- ProxyManager records it as initProgress.directConnect, which also reaches
  the failed answer's data. initProgress is reset per start().
- proxyInitTimeout words every branch for a direct connection, keyed on what
  is pending: while `initialize` is unanswered the endpoint has not spoken
  DAP, so the message names host:port, says nothing was started, and says to
  check that it is the listener meant, and that retrying the same port stalls
  the same way. Once `initialize` was answered the endpoint is a debug
  server and the stall is worded as its own. A connection never made no
  longer says to check that the adapter is installed.
- The worker's own report for a direct connection: an endpoint that never
  answered `initialize` in DAP (it hung up, or the request died on the wire)
  gets the same two sentences; any other failure ends "[direct connect to
  host:port; no adapter process was started]". A refusal in DAP
  (DapResponseError) is an answer, and keeps the adapter's own words.

The spawned-adapter sentences are byte for byte what they were (pinned). The
30 s deadline is untouched: a shorter one for direct connect would be a hold
sized without a measurement.

Tests written first and seen failing (13 red): every branch of the message,
with the four spawned-adapter sentences pinned whole; status -> progress ->
message in the proxy manager, and a status naming no usable endpoint; the
worker in connect mode (status sent before the transport connects, nothing
spawned; hang-up, DAP refusal, connection never made); both policies. New
real-server suite python-attach-wrong-port.test.ts: a listener that hangs up
on the first bytes, the exact answer.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…e endpoint report too (#1139)

Found by the independent review of this branch. A Python or Ruby attach to
a listener that resets the connection (a forwarder with nothing behind it)
still answered the bare "DAP client error: read ECONNRESET": the reset
reaches the worker as the socket's error event before the pending
`initialize` request rejects, the worker sent that first, and the parent
fails the attach on the first error it receives. The init catch composed
the endpoint report afterwards, for nobody.

While a direct-connect attach is still initializing, the socket-error
handler now sends the init-failure report itself — the same sentence a
hang-up gets, with the socket's own reason — and latches it, and the init
catch honours the latch, so the parent gets one report. Measured against
the built server with a listener that resets on the first bytes and one
that resets on accept: both answered "DAP client error: read|write
ECONNRESET" before, and "127.0.0.1:<port> never answered the DAP
"initialize" request (read|write ECONNRESET). Nothing was started for this
attach: …" after.

Unchanged, and pinned: a spawned adapter's two reports for a reset during
the handshake, and the plain report for a socket error on an established
direct connection.

Also pins what the review's mutation pass found unpinned: once `initialize`
was answered, a later failure does not doubt that the endpoint is a debug
server (dropping the `initializeAnswered` check, or never setting it, both
survived the unit project before).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…y the check's own rules (#1142)

Found by the independent review of this branch. When the statement
resolver did not find the expected text, the refusal said "That text
matches no line of the file" — the resolver's verdict worded as a fact
about the file. The resolver ignores comment lines and whatever follows a
`//` or `#`; expectedContent does not. So text that sat in a trailing
comment one line away, or past a `//` inside a string, or an expectation
carrying a comment the file no longer has, was declared absent while the
same expectedContent would have passed on the right line.

The line-matching rules move into one function, `matchLineContent`, which
`assertLineContent` reads for the line the caller named and the mismatch
message reads for every other line. With no statement form to offer, the
message names the lines the check passes on ("That expectedContent matches
line 3."), and says "matches no line of the file" only when it passes on
none. A comment expectation gets the same sentence (it used to get
nothing); text spanning lines still gets nothing.

The property test now holds the message to that: every line it names
passes the check, and "no line" means none does.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ect four doc claims (#1135, #1139, #1140, #1142)

Tests (each checked against the reviewer's surviving mutant, which it now
kills):
- evaluate_expression on a stack that reaches the program carries no
  noProgramFrame and no note (#1135);
- the never-connected direct-connect sentence without a policy phrase, and
  each half of the parent's endpoint guard on its own (#1139);
- a return-value entry is asserted as `new` when it appears (#1140);
- the two snapshot-diff suites land a launch that answers pending with
  wait_for_stop instead of assuming the launch hold was enough (#1140).

Docs:
- locals and evaluate carry the sentence of the stop's own answer, not
  get_stack_trace's (which words a hidden-frames stack differently), and
  the example is a launched program idle between timers (#1135);
- `data.initProgress.directConnect` rides only on the 30 s stall's answer;
  the reset case is described (#1139);
- the flagless `{after}` entry is in the skill text, and a frame whose
  every local left scope is documented as saying nothing (#1140);
- the expectedContent mismatch sentence as it now reads (#1142);
- `getLocalVariables`' return type in the API reference (#1135).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ted by endpoint too (#1139)

Found by the second review, of the reset fix. That fix covered a reset,
which reaches the worker as the client's `error` event. A hang-up reaches
it as the `close` event, and the worker answered a close with the
connection-closed status. Before `initialize` is answered the init catch
happens to report first; after it, with debugpy's attach order, the status
won, and the parent read it as the proxy exiting:

  Failed to attach: Proxy exited during initialization. Code: null,
  Signal: undefined
  Stderr output (last 10 of 39 lines): …worker debug lines…

A drop under the configuration requests answered "Error in DAP sequence:
DAP client disconnected". Neither names the endpoint or says that nothing
was started. Both need a listener that answers `initialize` and then goes
away: a debug server, or a forwarder to one, that dies mid-handshake.

The two socket events now share one path (`reportDirectConnectDrop`): the
first of them during a direct-connect handshake sends the init-failure
report and latches it; a later event, the init catch and the
configuration phase send nothing more. The client emits `close` before it
rejects what was pending, so the report is first by construction, not by
which continuation runs sooner.

Measured against the built server with a scripted DAP listener (answers
`initialize`, then drops), Python and Ruby:

  hang-up while attach is pending   Proxy exited during initialization…  (python)
  hang-up during configuration      Error in DAP sequence: DAP client disconnected
  both, after                       Critical initialization error: DAP client
                                    disconnected [direct connect to
                                    127.0.0.1:<port>; no adapter process was started]

Unchanged, and pinned: a close on an established direct connection sends
no error, and a close after a failure already reported adds nothing. A
reset after the worker's handshake completed, while the attach is still
being verified, still answers the plain "DAP client error: read
ECONNRESET": that is past the handshake this issue is about.

Also pins the cap on the list of lines in "That expectedContent matches
lines …" (#1142), which the same review found unpinned, and makes the
changelog say what each of the three first-contact failures reads.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The third review, of the hang-up fix, found the changelog claiming more
than was measured: a debug server that sends a DAP `terminated` event and
then closes during the handshake is still answered "Proxy exited during
initialization…", because the worker is already shutting down when the
socket closes. Not a regression, and seen only from a scripted listener;
it is #1152 now. The changelog and the troubleshooting guide say so.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment