Skip to content

Fix onTerminate being ignored on attach sessions - #2075

Merged
Rich Chiodo (rchiodo) merged 1 commit into
microsoft:mainfrom
Om-singhaI:fix-on-terminate-validated-as-bool
Sep 22, 2026
Merged

Rich Chiodo (rchiodo) merged 1 commit into
microsoft:mainfrom
Om-singhaI:fix-on-terminate-validated-as-bool

Conversation

@Om-singhaI

Copy link
Copy Markdown
Contributor

Fixes #2074

attach_request validates "onTerminate" with bool while launch_request validates it with str, and both compare the result against the string "KeyboardInterrupt". json.of_type(bool) doesn't reject a string, it coerces it, so on attach the option arrives as True and the comparison is always False. _forward_terminate_request stays False on every attach session, which leaves the delegate branch in terminate_request unreachable from attach. The client accepts the option and then kills the debuggee through session.finalize(..., terminate_debuggee=True), with no error anywhere to say the option was dropped.

"onTerminate" is a string option. The debug server reads it that way (pydevd_process_net_command_json.py uses args.get("onTerminate", "kill")), and pydevd's coverage for it only goes through launch (tests_python/test_debugger_json.py calls write_launch(onTerminate=...)), which is why the attach side stayed quiet.

What changed:

  • src/debugpy/adapter/clients.py: attach_request validates "onTerminate" with str, matching launch_request. One word.
  • tests/debugpy/adapter/test_clients.py: new parametrized test_attach_honors_on_terminate covering "KeyboardInterrupt", "kill", and the option being absent. It drives the real attach_request with a parsed DAP packet and reads back _forward_terminate_request.
  • The test stops the handler on the "listen" and "connect" mutual exclusion check, which is the first check after the option is read, so it opens no socket and spawns no session.
  • _FakeSession gained a launcher = None class attribute. Client.launcher proxies to the session, and the shared start request wrapper checks it before dispatching.

Testing, on macOS 26.6.2 with CPython 3.10.6 and PYTHONPATH=$PWD/src:

  • python3 -m pytest tests/debugpy/adapter/ -q -p no:cacheprovider -o addopts= gives 7 passed in 1.57s.
  • python3 -m pytest tests/debugpy/common/test_messaging.py -k "not fuzz" -q -p no:cacheprovider -o addopts= gives 13 passed, 1 deselected in 2.65s.
  • python3 -m pytest tests/debugpy/common/test_json.py tests/debugpy/common/test_singleton.py tests/debugpy/common/test_socket.py -q -p no:cacheprovider -o addopts= gives 12 passed in 2.88s.
  • python3 -m ruff check ., which is what the Lint stage runs, passes on the whole tree.
  • python3 -m flake8 src/debugpy/adapter/clients.py tests/debugpy/adapter/test_clients.py is clean, exit 0.
  • python3 -m black --check --diff on both files reports exactly the same hunks it reports for those files on main, so nothing new was added. Both already fail black --check upstream and I left that alone rather than mixing reformatting into this change.
  • Negative control: with src/debugpy/adapter/clients.py restored from main and the new test kept, test_attach_honors_on_terminate[arguments0-True] fails with assert False is True, 1 failed and 2 passed. The other two cases pass against main as well, since they assert False.

I didn't run tox, since commands_pre builds the attach binaries and this change needs no rebuild.

launch_request and attach_request now read "onTerminate" with the same three lines. Happy to hoist them into one helper so they can't drift again, but I kept this to the one word so the fix stays easy to review.

"onTerminate" is a string option whose graceful value is "KeyboardInterrupt".
launch_request validates it with str, and the debug server reads it as a
string too (pydevd_process_net_command_json.py uses
args.get("onTerminate", "kill")). attach_request validates it with bool.

json.of_type(bool) does not reject a string, it coerces it, so
"KeyboardInterrupt" arrives as True and the comparison that follows is
False. _forward_terminate_request therefore stays False on every attach
session, and no value of "onTerminate" can turn the option on.

The visible effect is in terminate_request. On launch it delegates to the
server, which interrupts the main thread, so finally blocks, atexit handlers
and buffered output all get their chance. On attach it always falls through
to session.finalize(terminate_debuggee=True), which kills the process and
its subprocesses. The client accepts the option and then ignores it.

Validate it as str, same as launch. The regression test drives the real
attach handler with a parsed DAP packet and checks the flag that
terminate_request reads. It stops the handler at the first argument check
after "onTerminate", so it opens no sockets.
@Om-singhaI
om singhal (Om-singhaI) requested a review from a team as a code owner September 22, 2026 19:05
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@rchiodo

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR.

on_terminate = request("onTerminate", str, optional=True)

if on_terminate:
self._forward_terminate_request = on_terminate == "KeyboardInterrupt"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning · Non-blocking recommendation

📍 src/debugpy/adapter/clients.py:452
A rejected attach with "onTerminate": "KeyboardInterrupt" leaves this flag enabled if the client retries without onTerminate, causing termination to forward Ctrl+C instead of using the default hard kill. Assign unconditionally with self._forward_terminate_request = on_terminate == "KeyboardInterrupt" and add a retry regression case.

[verified]

@rchiodo Rich Chiodo (rchiodo) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) added the review-auto:approved Automated review: no blocking findings (approval posted). label Sep 22, 2026
@rchiodo

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@bschnurr Bill Schnurr (bschnurr) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved via Review Center.

@rchiodo
Rich Chiodo (rchiodo) merged commit c43e22b into microsoft:main Sep 22, 2026
19 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"onTerminate": "KeyboardInterrupt" is ignored on attach sessions

3 participants