Problem: Concurrent first execution requests for the same environment and working directory create separate terminals instead of sharing initialization. Closing the older terminal then removes the cache entry for the newer, still-live terminal, causing the next request to create another terminal.
Tested: macOS arm64, Zsh, Python Environments development build from 4ab81cfabe1652f43f689eefcbd67eaa0f23ffef. The live reproduction used "python-envs.terminal.autoActivationType": "off"; no slow activation is required.
Reproduction
- Open a disposable project in a fresh development host and select a venv. Set terminal activation to
off and leave shell integration enabled.
- Create
probe.py with print("completed"), and ensure no execution terminal has already been cached for this project/environment.
- Run this async body from an extension-host test/helper extension, not a shell or browser Developer Tools:
const assert = require('node:assert/strict');
const path = require('node:path');
const vscode = require('vscode');
const api = await vscode.extensions.getExtension('ms-python.vscode-python-envs').activate();
const project = vscode.workspace.workspaceFolders[0].uri;
const environment = await api.getEnvironment(project);
assert.ok(environment, 'Select the disposable venv first');
const options = {
cwd: project.fsPath,
args: [path.join(project.fsPath, 'probe.py')],
show: true,
};
const [first, second] = await Promise.all([
api.runInTerminal(environment, options),
api.runInTerminal(environment, options),
]);
assert.notEqual(first, second, 'Baseline returns two distinct terminals');
const closed = new Promise(resolve => {
const subscription = vscode.window.onDidCloseTerminal(terminal => {
if (terminal === first) {
subscription.dispose();
resolve();
}
});
});
first.dispose();
await closed;
assert.equal(second.exitStatus, undefined, 'Second terminal remains alive');
const third = await api.runInTerminal(environment, options);
assert.notEqual(third, second, 'Baseline fails to reuse the live second terminal');
- Observe two distinct terminals from the concurrent calls and a different terminal from the third call, despite the second remaining alive.
Actual result: Both initial scripts execute using the correct Python, but initialization is duplicated. Closing the older terminal invalidates reuse of the newer terminal.
Expected result: Same-key concurrent creation should share a terminal/initialization. Regardless, closing an older terminal must not delete a cache entry currently owned by another live terminal.
Captured evidence
The real-host probe used separate output files to verify both initial scripts completed with the selected interpreter:
{"stage":"concurrent-first-runs","distinctTerminals":true}
{"stage":"after-close-older","reusedLiveSecond":false,"secondStillLive":true}
These are concurrent public API calls, not a claim that two physical toolbar clicks were tested. Terminal object identity is the evidence; total tab counts can include unrelated terminals.
Source evidence
The live-host reproduction above covers project terminals with activation off. It demonstrates duplicate creation and failure to reuse the remaining live terminal, but does not instrument which terminal last populated the cache key. The Promise.all result order is not proof of cache-write order.
Deterministic production-class probes that control that ordering reproduce the incorrect eviction directly for both project and dedicated caches in off and command modes: the newer terminal is the cached one, closing the older terminal deletes the shared key, and the next request creates a third terminal while the second remains live. Their recorded result was:
{"result":"BUG","stillLiveTerminal":2,"returnedTerminal":3,"terminalsCreated":3}
Steps to verify a fix
- Start concurrent same-key requests and verify initialization produces one reusable execution terminal.
- Verify closing an older terminal cannot evict a newer terminal stored under the same key.
- Verify sequential requests still reuse a live terminal.
- Verify different environments/working directories retain their separate terminals.
- Close the currently cached terminal and verify the next request creates a replacement.
Problem: Concurrent first execution requests for the same environment and working directory create separate terminals instead of sharing initialization. Closing the older terminal then removes the cache entry for the newer, still-live terminal, causing the next request to create another terminal.
Tested: macOS arm64, Zsh, Python Environments development build from
4ab81cfabe1652f43f689eefcbd67eaa0f23ffef. The live reproduction used"python-envs.terminal.autoActivationType": "off"; no slow activation is required.Reproduction
offand leave shell integration enabled.probe.pywithprint("completed"), and ensure no execution terminal has already been cached for this project/environment.Actual result: Both initial scripts execute using the correct Python, but initialization is duplicated. Closing the older terminal invalidates reuse of the newer terminal.
Expected result: Same-key concurrent creation should share a terminal/initialization. Regardless, closing an older terminal must not delete a cache entry currently owned by another live terminal.
Captured evidence
The real-host probe used separate output files to verify both initial scripts completed with the selected interpreter:
{"stage":"concurrent-first-runs","distinctTerminals":true} {"stage":"after-close-older","reusedLiveSecond":false,"secondStillLive":true}These are concurrent public API calls, not a claim that two physical toolbar clicks were tested. Terminal object identity is the evidence; total tab counts can include unrelated terminals.
Source evidence
The live-host reproduction above covers project terminals with activation off. It demonstrates duplicate creation and failure to reuse the remaining live terminal, but does not instrument which terminal last populated the cache key. The
Promise.allresult order is not proof of cache-write order.Deterministic production-class probes that control that ordering reproduce the incorrect eviction directly for both project and dedicated caches in
offandcommandmodes: the newer terminal is the cached one, closing the older terminal deletes the shared key, and the next request creates a third terminal while the second remains live. Their recorded result was:{"result":"BUG","stillLiveTerminal":2,"returnedTerminal":3,"terminalsCreated":3}Steps to verify a fix