sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) [9 Security Hotspots](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL) [5 Code Smells](https://sonarcloud.io/project/issues?id=microsoft_debugpy&pullRequest=137&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='95.4%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list) [95.4% Coverage](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=microsoft_debugpy&pullRequest=137&metric=new_duplicated_lines_density&view=list)


diff --git a/.vscode/launch.json b/.vscode/launch.json
index 6a535701..eacf2a46 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,50 +5,71 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
-            "name": "Launch Python file",
+            "name": "Launch: program",
             "type": "python",
             "request": "launch",
             "console": "integratedTerminal",
-            "consoleTitle": "ptvsd.server",
             "program": "${file}",
             "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
+            "name": "Launch: module",
+            "type": "python",
+            "request": "launch",
+            "console": "integratedTerminal",
+            "module": "${fileBasenameNoExtension}",
+            "cwd": "${fileDirname}",
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
+        },
+        {
+            "name": "Launch: code",
+            "type": "python",
+            "request": "launch",
+            "console": "integratedTerminal",
+            "code": [
+                "import runpy",
+                "runpy.run_path(r'${file}')",
+            ],
+            "logToFile": true,
             "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
+        },
+        {
             "name": "Attach: connect",
             "type": "python",
             "request": "attach",
-            "logToFile": true,
             "connect": {
                 "port": 5678,
                 "host": "127.0.0.1",
-            }
+            },
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Attach: listen",
             "type": "python",
             "request": "attach",
-            "logToFile": true,
             "listen": {
                 "port": 5678,
                 "host": "127.0.0.1",
-            }
+            },
+            "logToFile": true,
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Attach: PID",
             "type": "python",
             "request": "attach",
-            "processId": "${command:pickProcess}"
+            "processId": "${command:pickProcess}",
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         },
         {
-            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
             "name": "Debug Tests",
             "type": "python",
             "request": "test",
             "console": "integratedTerminal",
+            "debugAdapterPath": "${workspaceFolder}/src/debugpy/adapter",
         }
     ]
 }
\ No newline at end of file
diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py
index d1ad18e0..a69a9161 100644
--- a/src/debugpy/adapter/clients.py
+++ b/src/debugpy/adapter/clients.py
@@ -8,7 +8,7 @@ import os
 import sys
 
 import debugpy
-from debugpy import adapter
+from debugpy import adapter, launcher
 from debugpy.common import fmt, json, log, messaging, sockets
 from debugpy.common.compat import unicode
 from debugpy.adapter import components, servers, sessions
@@ -272,22 +272,41 @@ class Client(components.Component):
 
             return value
 
-        # Launcher doesn't use the command line at all, but we pass the arguments so
-        # that they show up in the terminal if we're using "runInTerminal".
+        # Propagate command line arguments via launcher CLI rather than "args", so that
+        # they get shell expansion applied to them in "runInTerminal" scenarios.
+
+        program = module = code = ()
         if "program" in request:
-            args = request("program", json.array(unicode, vectorize=True, size=(1,)))
-        elif "module" in request:
-            args = ["-m"] + request(
-                "module", json.array(unicode, vectorize=True, size=(1,))
+            program = request("program", unicode)
+            args = [program]
+            # process_name = program
+        if "module" in request:
+            module = request("module", unicode)
+            args = ["-m", module]
+            # process_name = module
+        if "code" in request:
+            code = request("code", json.array(unicode, vectorize=True, size=(1,)))
+            args = ["-c", "\n".join(code)]
+            # process_name = cmdline[0]
+
+        num_targets = len([x for x in (program, module, code) if x != ()])
+        if num_targets == 0:
+            raise request.isnt_valid(
+                'either "program", "module", or "code" must be specified'
             )
-        elif "code" in request:
-            args = ["-c"] + request(
-                "code", json.array(unicode, vectorize=True, size=(1,))
+        elif num_targets != 1:
+            raise request.isnt_valid(
+                '"program", "module", and "code" are mutually exclusive'
             )
-        else:
-            args = []
+
         args += request("args", json.array(unicode))
 
+        cwd = request("cwd", unicode, optional=True)
+        if cwd == ():
+            # If it's not specified, but we're launching a file rather than a module,
+            # and the specified path has a directory in it, use that.
+            cwd = None if program == () else (os.path.dirname(program) or None)
+
         console = request(
             "console",
             json.enum(
@@ -303,9 +322,18 @@ class Client(components.Component):
         if sudo and sys.platform == "win32":
             raise request.cant_handle('"sudo":true is not supported on Windows.')
 
+        launcher_path = request("debugLauncherPath", os.path.dirname(launcher.__file__))
+
         servers.serve()
         launchers.spawn_debuggee(
-            self.session, request, args, console, console_title, sudo
+            self.session,
+            request,
+            launcher_path,
+            args,
+            cwd,
+            console,
+            console_title,
+            sudo,
         )
 
     @_start_message_handler
diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py
index 1a91f4b3..ce502fb8 100644
--- a/src/debugpy/adapter/launchers.py
+++ b/src/debugpy/adapter/launchers.py
@@ -8,7 +8,7 @@ import os
 import subprocess
 import sys
 
-from debugpy import adapter, launcher
+from debugpy import adapter
 from debugpy.common import compat, log, messaging, sockets
 from debugpy.adapter import components, servers
 
@@ -65,13 +65,13 @@ class Launcher(components.Component):
                     pass
 
 
-def spawn_debuggee(session, start_request, args, console, console_title, sudo):
+def spawn_debuggee(
+    session, start_request, launcher_path, args, cwd, console, console_title, sudo
+):
     # -E tells sudo to propagate environment variables to the target process - this
     # is necessary for launcher to get DEBUGPY_LAUNCHER_PORT and DEBUGPY_LOG_DIR.
     cmdline = ["sudo", "-E"] if sudo else []
-
-    cmdline += [sys.executable, os.path.dirname(launcher.__file__)]
-    cmdline += args
+    cmdline += [sys.executable, launcher_path]
     env = {}
 
     arguments = dict(start_request.arguments)
@@ -90,15 +90,14 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
         )
     except Exception as exc:
         raise start_request.cant_handle(
-            "{0} couldn't create listener socket for launcher: {1}",
-            session,
-            exc,
+            "{0} couldn't create listener socket for launcher: {1}", session, exc
         )
 
     try:
         _, launcher_port = listener.getsockname()
+        cmdline += [str(launcher_port), "--"]
+        cmdline += args
 
-        env[str("DEBUGPY_LAUNCHER_PORT")] = str(launcher_port)
         if log.log_dir is not None:
             env[str("DEBUGPY_LOG_DIR")] = compat.filename_str(log.log_dir)
         if log.stderr.levels != {"warning", "error"}:
@@ -107,11 +106,20 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
         if console == "internalConsole":
             log.info("{0} spawning launcher: {1!r}", session, cmdline)
             try:
+                for i, arg in enumerate(cmdline):
+                    try:
+                        cmdline[i] = compat.filename_str(arg)
+                    except UnicodeEncodeError as exc:
+                        raise start_request.cant_handle(
+                            "Invalid command line argument {0!j}: {1}", arg, exc
+                        )
+
                 # If we are talking to the client over stdio, sys.stdin and sys.stdout
                 # are redirected to avoid mangling the DAP message stream. Make sure
                 # the launcher also respects that.
                 subprocess.Popen(
                     cmdline,
+                    cwd=cwd,
                     env=dict(list(os.environ.items()) + list(env.items())),
                     stdin=sys.stdin,
                     stdout=sys.stdout,
@@ -123,22 +131,24 @@ def spawn_debuggee(session, start_request, args, console, console_title, sudo):
             log.info('{0} spawning launcher via "runInTerminal" request.', session)
             session.client.capabilities.require("supportsRunInTerminalRequest")
             kinds = {"integratedTerminal": "integrated", "externalTerminal": "external"}
+            request_args = {
+                "kind": kinds[console],
+                "title": console_title,
+                "args": cmdline,
+                "env": env,
+            }
+            if cwd is not None:
+                request_args["cwd"] = cwd
             try:
-                session.client.channel.request(
-                    "runInTerminal",
-                    {
-                        "kind": kinds[console],
-                        "title": console_title,
-                        "args": cmdline,
-                        "env": env,
-                    },
-                )
+                session.client.channel.request("runInTerminal", request_args)
             except messaging.MessageHandlingError as exc:
                 exc.propagate(start_request)
 
         # If using sudo, it might prompt for password, and launcher won't start running
         # until the user enters it, so don't apply timeout in that case.
-        if not session.wait_for(lambda: session.launcher, timeout=(None if sudo else 10)):
+        if not session.wait_for(
+            lambda: session.launcher, timeout=(None if sudo else 10)
+        ):
             raise start_request.cant_handle("Timed out waiting for launcher to connect")
 
         try:
diff --git a/src/debugpy/launcher/__init__.py b/src/debugpy/launcher/__init__.py
index 4ec59ac5..14b7a40b 100644
--- a/src/debugpy/launcher/__init__.py
+++ b/src/debugpy/launcher/__init__.py
@@ -16,7 +16,7 @@ channel = None
 """DAP message channel to the adapter."""
 
 
-def connect(launcher_port):
+def connect(host, port):
     from debugpy.common import messaging, sockets
     from debugpy.launcher import handlers
 
@@ -24,7 +24,7 @@ def connect(launcher_port):
     assert channel is None
 
     sock = sockets.create_client()
-    sock.connect(("127.0.0.1", launcher_port))
+    sock.connect((host, port))
 
     stream = messaging.JsonIOStream.from_socket(sock, "Adapter")
     channel = messaging.JsonMessageChannel(stream, handlers=handlers)
diff --git a/src/debugpy/launcher/__main__.py b/src/debugpy/launcher/__main__.py
index 4866caa5..482ef4e2 100644
--- a/src/debugpy/launcher/__main__.py
+++ b/src/debugpy/launcher/__main__.py
@@ -31,15 +31,22 @@ def main():
     # will also exit, so it doesn't need to observe the signal directly.
     signal.signal(signal.SIGINT, signal.SIG_IGN)
 
-    def option(name, type, *args):
-        try:
-            return type(os.environ.pop(name, *args))
-        except Exception:
-            log.reraise_exception("Error parsing {0!r}:", name)
-
-    launcher_port = option("DEBUGPY_LAUNCHER_PORT", int)
-
-    launcher.connect(launcher_port)
+    # Everything before "--" is command line arguments for the launcher itself,
+    # and everything after "--" is command line arguments for the debuggee.
+    sep = sys.argv.index("--")
+    launcher_argv = sys.argv[1:sep]
+    sys.argv = [sys.argv[0]] + sys.argv[sep + 1:]
+
+    # The first argument specifies the host/port on which the adapter is waiting
+    # for launcher to connect. It's either host:port, or just port.
+    adapter = launcher_argv[0]
+    host, sep, port = adapter.partition(":")
+    if not sep:
+        host = "127.0.0.1"
+        port = adapter
+    port = int(port)
+
+    launcher.connect(host, port)
     launcher.channel.wait()
 
     if debuggee.process is not None:
diff --git a/src/debugpy/launcher/debuggee.py b/src/debugpy/launcher/debuggee.py
index 9290a786..fc0b1c0c 100644
--- a/src/debugpy/launcher/debuggee.py
+++ b/src/debugpy/launcher/debuggee.py
@@ -31,13 +31,11 @@ def describe():
     return fmt("Debuggee[PID={0}]", process.pid)
 
 
-def spawn(process_name, cmdline, cwd, env, redirect_output):
+def spawn(process_name, cmdline, env, redirect_output):
     log.info(
         "Spawning debuggee process:\n\n"
-        "Current directory: {0!r}\n\n"
-        "Command line: {1!r}\n\n"
-        "Environment variables: {2!r}\n\n",
-        cwd,
+        "Command line: {0!r}\n\n"
+        "Environment variables: {1!r}\n\n",
         cmdline,
         env,
     )
@@ -56,7 +54,7 @@ def spawn(process_name, cmdline, cwd, env, redirect_output):
 
         try:
             global process
-            process = subprocess.Popen(cmdline, cwd=cwd, env=env, bufsize=0, **kwargs)
+            process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs)
         except Exception as exc:
             raise messaging.MessageHandlingError(
                 fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline)
diff --git a/src/debugpy/launcher/handlers.py b/src/debugpy/launcher/handlers.py
index b353a24c..811bc554 100644
--- a/src/debugpy/launcher/handlers.py
+++ b/src/debugpy/launcher/handlers.py
@@ -67,37 +67,14 @@ def launch_request(request):
         debugpy_args = request("debugpyArgs", json.array(unicode))
         cmdline += debugpy_args
 
-    program = module = code = ()
-    if "program" in request:
-        program = request("program", unicode)
-        cmdline += [program]
-        process_name = program
-    if "module" in request:
-        module = request("module", unicode)
-        cmdline += ["-m", module]
-        process_name = module
-    if "code" in request:
-        code = request("code", json.array(unicode, vectorize=True, size=(1,)))
-        cmdline += ["-c", "\n".join(code)]
-        process_name = cmdline[0]
-
-    num_targets = len([x for x in (program, module, code) if x != ()])
-    if num_targets == 0:
-        raise request.isnt_valid(
-            'either "program", "module", or "code" must be specified'
-        )
-    elif num_targets != 1:
-        raise request.isnt_valid(
-            '"program", "module", and "code" are mutually exclusive'
-        )
-
-    cmdline += request("args", json.array(unicode))
-
-    cwd = request("cwd", unicode, optional=True)
-    if cwd == ():
-        # If it's not specified, but we're launching a file rather than a module,
-        # and the specified path has a directory in it, use that.
-        cwd = None if program == () else (os.path.dirname(program[0]) or None)
+    # Use command line arguments propagated via launcher CLI, rather than "args", to get
+    # their values after shell expansion in "runInTerminal" scenarios. The command line
+    # parser in __main__ has already removed everything up to and including "--" by now.
+    cmdline += sys.argv[1:]
+
+    # We want the process name to reflect the target. So for -m, use the module name
+    # that follows; otherwise use the first argument, which is either -c or filename.
+    process_name = compat.filename(sys.argv[1] if sys.argv[1] != "-m" else sys.argv[2])
 
     env = os.environ.copy()
     env_changes = request("env", json.object(unicode))
@@ -161,7 +138,7 @@ def launch_request(request):
         cmdline = [encode(s) for s in cmdline]
         env = {encode(k): encode(v) for k, v in env.items()}
 
-    debuggee.spawn(process_name, cmdline, cwd, env, redirect_output)
+    debuggee.spawn(process_name, cmdline, env, redirect_output)
     return {}
 
 

Shell expansion is not working in "args"
## Environment data

Version: 1.43.1
Commit: fe22a9645b44368865c0ba92e2fb881ff1afce94
Date: 2020-03-18T07:17:38.324Z
Electron: 7.1.11
Chrome: 78.0.3904.130
Node.js: 12.8.1
V8: 7.8.279.23-electron.0
OS: Linux x64 5.5.8-1-MANJARO

## Expected behaviour

On launch of a run configuration, environment variable calls like $(XXX) or ${XXX} defined in the args section are escaped by putting it between quotation marks.

So if for instance `--arg-xyz=$(XXX)` is put into the args section and should evaluate into 

`--arg-xyz=WHATEVER_IS_THE_OUTPUT_OF_THE_CALL`

## Actual behaviour

`--arg-xyz=$(XXX)` is evaluated to `--arg-xyz="$(XXX)"` what causes the python program launch to fail.

## Steps to reproduce:

Actually I don't know, I used this configuration since months and today it did stop working by some kind of escaping these evaluations by putting it just into the quotation marks.

So the launch.json config:

```
{
            "name": "Odoo",
            "type": "python",
            "request": "launch",
            "program": "/odoo/odoo-bin",
            "args": [
                "--config",
                "/workspace/.devcontainer/etc/odoo.conf",
                "--addons-path",
                "$(gen_addons_path -d /addons)",       <<<<< THIS IS EVALUATED WRONG!
                "--load",
                "base,web,base_test_db",
                "--dev=xml,qweb",      
            ],
            "console": "integratedTerminal",
            "justMyCode": false
        }
```

will result in this actual call of the program:

```
env DEBUGPY_LAUNCHER_PORT=40595 /usr/bin/python3 /var/lib/odoo/.vscode-server/extensions/ms-python.python-2020.3.69010/pythonFiles/lib/python/debugpy/wheels/debugpy/launcher /odoo/odoo-bin --config /workspace/.devcontainer/etc/odoo.conf --addons-path "$(gen_addons_path -d /addons)" --load base,web,base_test_db --dev=xml,qweb 
Usage: odoo-bin [options]

odoo-bin: error: option --addons-path: no such directory: '$(gen_addons_path -d /addons)'
```

As you see here, $(gen_addons_path -d /addons) is not evaluated, but just put in a string.

I would be very grateful for any hints to get this working again...


Thanks in advance!
diff --git a/tests/debug/__init__.py b/tests/debug/__init__.py
index 5e5995d6..d721c7b6 100644
--- a/tests/debug/__init__.py
+++ b/tests/debug/__init__.py
@@ -4,9 +4,5 @@
 
 from __future__ import absolute_import, division, print_function, unicode_literals
 
-
 # Expose Session directly.
-def Session(*args, **kwargs):
-    from tests.debug import session
-
-    return session.Session(*args, **kwargs)
+from tests.debug.session import Session  # noqa
diff --git a/tests/debug/session.py b/tests/debug/session.py
index 2081ee6e..2b391063 100644
--- a/tests/debug/session.py
+++ b/tests/debug/session.py
@@ -484,6 +484,12 @@ class Session(object):
                 pid, fmt("{0}-subprocess-{1}", self.debuggee_id, pid)
             )
 
+    def run_in_terminal(self, args, cwd, env):
+        exe = args.pop(0)
+        self.spawn_debuggee.env.update(env)
+        self.spawn_debuggee(args, cwd, exe=exe)
+        return {}
+
     def _process_request(self, request):
         self.timeline.record_request(request, block=False)
         if request.command == "runInTerminal":
@@ -491,11 +497,8 @@ class Session(object):
             cwd = request("cwd", ".")
             env = request("env", json.object(unicode))
             try:
-                exe = args.pop(0)
-                self.spawn_debuggee.env.update(env)
-                self.spawn_debuggee(args, cwd, exe=exe)
-                return {}
-            except OSError as exc:
+                return self.run_in_terminal(args, cwd, env)
+            except Exception as exc:
                 log.swallow_exception('"runInTerminal" failed:')
                 raise request.cant_handle(str(exc))
         else:
diff --git a/tests/debugpy/test_args.py b/tests/debugpy/test_args.py
index 6a74b97f..d8d728de 100644
--- a/tests/debugpy/test_args.py
+++ b/tests/debugpy/test_args.py
@@ -31,3 +31,37 @@ def test_args(pyfile, target, run):
             pass
         argv = backchannel.receive()
         assert argv == [some.str] + args
+
+
+@pytest.mark.parametrize("target", targets.all)
+@pytest.mark.parametrize("run", runners.all_launch)
+def test_shell_expansion(pyfile, target, run):
+    @pyfile
+    def code_to_debug():
+        import sys
+        import debuggee
+        from debuggee import backchannel
+
+        debuggee.setup()
+        backchannel.send(sys.argv)
+
+    def expand(args):
+        for i, arg in enumerate(args):
+            if arg.startswith("$"):
+                args[i] = arg[1:]
+
+    class Session(debug.Session):
+        def run_in_terminal(self, args, cwd, env):
+            expand(args)
+            return super(Session, self).run_in_terminal(args, cwd, env)
+
+    args = ["0", "$1", "2"]
+    with Session() as session:
+        backchannel = session.open_backchannel()
+        with run(session, target(code_to_debug, args=args)):
+            pass
+        argv = backchannel.receive()
+
+    if session.config["console"] != "internalConsole":
+        expand(args)
+    assert argv == [some.str] + args

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio pytest
3.9
tests/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asgiref==3.8.1
atomicwrites==1.4.1
attrs==25.3.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
-e git+https://github.com/microsoft/debugpy.git@011810aa420f751492395c7251cf6e730b945b74#egg=debugpy
distlib==0.3.9
Django==4.2.20
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.18.0
Flask==1.0.3
gevent==24.11.1
greenlet==3.1.1
idna==3.10
iniconfig==2.1.0
itsdangerous==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.2
more-itertools==10.6.0
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
psutil==7.0.0
py==1.11.0
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-forked==1.6.0
pytest-mock==3.14.0
pytest-timeout==1.4.2
pytest-xdist==1.34.0
requests==2.32.3
six==1.17.0
sqlparse==0.5.3
tomli==2.2.1
tox==3.28.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3
wcwidth==0.2.13
Werkzeug==3.1.3
zope.event==5.0
zope.interface==7.2

name: debugpy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asgiref==3.8.1
      - atomicwrites==1.4.1
      - attrs==25.3.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - debugpy==1.0.0b7+2.g011810aa
      - distlib==0.3.9
      - django==4.2.20
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - filelock==3.18.0
      - flask==1.0.3
      - gevent==24.11.1
      - greenlet==3.1.1
      - idna==3.10
      - iniconfig==2.1.0
      - itsdangerous==2.2.0
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - more-itertools==10.6.0
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - psutil==7.0.0
      - py==1.11.0
      - pytest==8.3.5
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-forked==1.6.0
      - pytest-mock==3.14.0
      - pytest-timeout==1.4.2
      - pytest-xdist==1.34.0
      - requests==2.32.3
      - six==1.17.0
      - sqlparse==0.5.3
      - tomli==2.2.1
      - tox==3.28.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - wcwidth==0.2.13
      - werkzeug==3.1.3
      - zope-event==5.0
      - zope-interface==7.2
prefix: /opt/conda/envs/debugpy

tests/debugpy/test_args.py::test_shell_expansion[launch(integratedTerminal)-program] tests/debugpy/test_args.py::test_shell_expansion[launch(integratedTerminal)-module] tests/debugpy/test_args.py::test_shell_expansion[launch(externalTerminal)-code] tests/debugpy/test_args.py::test_shell_expansion[launch(integratedTerminal)-code] tests/debugpy/test_args.py::test_shell_expansion[launch(externalTerminal)-program] tests/debugpy/test_args.py::test_shell_expansion[launch(externalTerminal)-module]
tests/debugpy/test_args.py::test_args[attach_pid-program] tests/debugpy/test_args.py::test_args[attach_pid-module] tests/debugpy/test_args.py::test_args[attach_pid-code]
tests/debugpy/test_args.py::test_args[launch(internalConsole)-program] tests/debugpy/test_args.py::test_args[launch(externalTerminal)-program] tests/debugpy/test_args.py::test_args[launch(integratedTerminal)-module] tests/debugpy/test_args.py::test_args[launch(externalTerminal)-module] tests/debugpy/test_args.py::test_args[launch(internalConsole)-code] tests/debugpy/test_args.py::test_args[launch(internalConsole)-module] tests/debugpy/test_args.py::test_args[launch(integratedTerminal)-code] tests/debugpy/test_args.py::test_args[launch(integratedTerminal)-program] tests/debugpy/test_args.py::test_args[attach_connect(api)-program] tests/debugpy/test_args.py::test_args[launch(externalTerminal)-code] tests/debugpy/test_args.py::test_args[attach_listen(cli)-code] tests/debugpy/test_args.py::test_args[attach_listen(cli)-program] tests/debugpy/test_args.py::test_args[attach_listen(api)-module] tests/debugpy/test_args.py::test_args[attach_listen(api)-program] tests/debugpy/test_args.py::test_args[attach_listen(cli)-module] tests/debugpy/test_args.py::test_args[attach_listen(api)-code] tests/debugpy/test_args.py::test_args[attach_connect(api)-module] tests/debugpy/test_args.py::test_args[attach_connect(cli)-module] tests/debugpy/test_args.py::test_args[attach_connect(cli)-program] tests/debugpy/test_args.py::test_args[attach_connect(cli)-code] tests/debugpy/test_args.py::test_args[attach_connect(api)-code] tests/debugpy/test_args.py::test_shell_expansion[launch(internalConsole)-module] tests/debugpy/test_args.py::test_shell_expansion[launch(internalConsole)-program] tests/debugpy/test_args.py::test_shell_expansion[launch(internalConsole)-code]
src/debugpy/adapter/clients.py src/debugpy/adapter/launchers.py src/debugpy/launcher/__main__.py src/debugpy/launcher/debuggee.py src/debugpy/launcher/handlers.py .vscode/launch.json src/debugpy/launcher/__init__.py
src/debugpy/adapter/clients.py src/debugpy/adapter/launchers.py src/debugpy/launcher/__main__.py src/debugpy/launcher/debuggee.py src/debugpy/launcher/handlers.py .vscode/launch.json src/debugpy/launcher/__init__.py