Skip to content

Activation commands sent to Pseudoterminals owned by other extensions #1482

@minestarks

Description

@minestarks

Description

The extension sends environment activation commands to all visible terminals on open, including Pseudoterminals created by other extensions. Since Pseudoterminals don't run a shell, the activation text arrives in handleInput as raw input, causing unintended side-effects.

Repro Steps

  1. Install both the Python Environments extension and an extension that creates a visible Pseudoterminal (e.g., QDK's "Run Program" terminal).
  2. Leave Python auto-activation at the default (python-envs.terminal.autoActivationType: "command").
  3. Run a Q# program to open the Pseudoterminal.

Expected

The Pseudoterminal should not receive any input.

Actual

The terminal's handleInput receives (Set-ExecutionPolicy …) ; (& …\Activate.ps1) shortly after opening.
In the QDK case, handleInput interprets any input after program completion as "close the terminal", so the injected activation command closes the terminal prematurely.

Logs

2026-04-25 09:08:36.514 [debug] Activating terminal: <my_path>\.venv\Scripts\python.exe
2026-04-25 09:08:36.514 [info] Terminal is activated: <my_path>\.venv\Scripts\python.exe

Root Cause

In terminalManager.ts (line 97), terminals are skipped only if they are in skipActivationOnOpen, have hideFromUser: true, or are task terminals:

if (this.skipActivationOnOpen.has(t) || (t.creationOptions as TerminalOptions)?.hideFromUser) {
    return;
}

There is no check for Pseudoterminals (ExtensionTerminalOptions with a pty property). A visible, non-task Pseudoterminal passes all filters.

Suggested Fix

Check for pty in creationOptions before activating:

this.onTerminalOpened(async (t) => {
    if (
        this.skipActivationOnOpen.has(t) ||
        (t.creationOptions as TerminalOptions)?.hideFromUser ||
        'pty' in t.creationOptions
    ) {
        return;
    }
    // ...
});

The same guard should be added in terminalActivationState.ts activate() and in initialize().

Example: Affected Extension (QDK)

The QDK extension creates a visible Pseudoterminal for running Q# programs:

hideFromUser is not set (the terminal is intentionally user-visible) and it is not a task terminal, so it passes all existing filters.

Workaround (for affected extension authors)

Filter out long input strings in handleInput — real keypresses are 1–4 bytes, while injected sendText commands are much longer:

handleInput: (data) => {
    if (data.length > 4) return; // ignore injected commands
    // ... handle real keypresses
}

Version

Extension version: 1.29.2026042401
OS: Windows
VS Code version: 1.117.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions