Summary
The pet binary (python-env-tools, shipped by ms-python.vscode-python-envs) busy-loops inside a filesystem stat/close scan when handling a configure JSON-RPC request whose environmentDirectories glob is evaluated against a large workspace. The scan does not complete within the extension's 30-second configure timeout, so the extension kills pet (SIGTERM), restarts it, and re-sends configure — which hangs again. After 3 attempts (~90 s of sustained 100% CPU on one core) the extension gives up.
On a thermally-constrained laptop this ~90 s 100% CPU spike pushes the package past 105 °C and the embedded controller cuts power with no OS-level log (no OOM, no panic, no thermal trip in journald — the journal just ends mid-normal-activity). The bug therefore masquerades as a hardware/BIOS issue and is very hard to diagnose. Worth fixing for that reason alone, not just the wasted CPU.
Environment
- Extension:
ms-python.vscode-python-envs v1.20.1 — the latest version compatible with my VS Code. (I have 1.22.0–1.36.0 installed too, but VS Code refuses to load them: "Extension is not compatible with Code 1.108.1. Extension requires: ^1.110.0-20260204". So I can't test newer versions without also upgrading VS Code. Happy to re-test on 1.36.0 if a maintainer confirms whether the scan/timeout behavior changed.)
- VS Code:
1.108.1
- OS: Ubuntu 24.04.3 LTS, kernel
6.17.0-22-generic, x86_64
pet binary: ELF 64-bit, static-pie, with debug_info, not stripped
- Hardware: HP OMEN laptop, Intel Core Ultra (Arrow Lake-H) + NVIDIA RTX 5070 Laptop (degraded cooling — needs repaste)
The trigger workspace
The crash only happens for one of my open workspaces. Its size is the key:
$ find /home/<user>/workspace/large-project | wc -l
279488
$ du -sh /home/<user>/workspace/large-project
101G
279,488 files / 101 GB, including several node_modules trees with symlinks. The other workspaces I have open (346 / ~small files) configure in <1 s and never spin. The configure params the extension sends for this workspace:
{"workspaceDirectories":["/home/<user>/workspace/large-project"],
"environmentDirectories":["/home/<user>/workspace/large-project/**/.venv"],
"pipenvExecutable":"pipenv","poetryExecutable":"poetry",
"cacheDirectory":".../pythonLocator"}
Note the **/.venv glob — pet walks the entire 279k-file tree looking for .venv dirs.
Reproduction (CLI, deterministic, ~12 s)
pet server reads JSON-RPC over stdio with Content-Length framing. Sending the exact configure request the extension sends hangs pet in a stat/close scan loop:
$ # request body in configure.json (the JSON above)
$ ( printf "Content-Length: %s\n\n" "$(wc -c < configure.json)"; cat configure.json; sleep 13 ) \
| timeout -s INT 12 strace -c -f -e trace=futex,poll,ppoll,read,write,stat,newfstatat,openat,close \
~/.vscode/extensions/ms-python.vscode-python-envs-1.20.1-linux-x64/python-env-tools/bin/pet server
# exit code 124 (pet never responded within 12 s)
strace summary (12 s, no pet find, just configure):
% time seconds usecs/call calls errors syscall
72.50 0.203708 1 138906 close
27.50 0.077264 11 6792 4317 stat
0.00 0.000000 0 1 read
0.00 0.000000 0 1 poll
100.00 0.280972 1 145700 4317 total
So pet is in a tight stat/close loop walking the workspace, 64% of stats returning ENOENT (probing for .venv/python/etc. at every entry). 12 s wasn't enough to finish; the extension's 30 s timeout isn't either.
Extension log (from the actual crash session)
~/.config/Code/logs/20260829T164624/window2/exthost/ms-python.vscode-python-envs/Python Environments.log:
16:47:32.185 [info] [pet] Starting Python Locator .../pet server
16:47:32.186 [info] [pet] configure: Sending configuration update: {"workspaceDirectories":[".../<large-workspace>"],"environmentDirectories":[".../<large-workspace>/**/.venv"],...}
16:48:02.186 [warning] [pet] Configure request timed out, killing hung process for restart
16:48:02.186 [error] [pet] configure: Configuration error Request 'configure' timed out after 30000ms
16:48:02.187 [warning] [pet] Restarting Python Environment Tools (attempt 3/3, waiting 4000ms)
16:48:02.188 [error] [pet] Python Environment Tools exited unexpectedly with code null, signal SIGTERM
16:48:06.195 [info] [pet] Starting Python Locator .../pet server # restart, send configure again
16:48:36.197 [warning] [pet] Configure request timed out, killing hung process for restart # 30s later, again
16:48:36.197 [error] PET failed after 3 restart attempts.
16:48:36.199 [warning] [priorityChain] .../<large-workspace> defaultInterpreterPath '/bin/python3' unresolvable, falling back to auto-discovery
Same pattern reproduces every time this workspace is opened (also seen in the 20260829T213146 session). Other workspaces configure in <1 s.
Impact / why this matters
Secondary issue: pet busy-loops on malformed input too
While figuring out the framing I sent a request without a valid Content-Length header. Instead of failing gracefully, pet entered an infinite error-spam loop writing the same error to stderr — 941,762 write calls in 12 s (~78k writes/s), 100% CPU:
% time calls syscall
58% 941762 write
24% 98908 open
11% 188354 read
...
stderr was the error message recursively nesting itself. A malformed request should error once and continue, not spin. Likely the same lack of backoff/bounding that causes the configure hang.
What I'd suggest (and can help validate)
- Bound the
configure scan: skip / prune common heavy dirs (node_modules, .git, __pycache__, .venv contents), cap depth or entry count, or make discovery incremental/lazy instead of a full synchronous walk that must finish before configure responds.
- Don't retry 3× with no backoff: a workspace that times out once will time out 3 times — that's 90 s of guaranteed 100% CPU. Exponential backoff or a single retry would cut the blast radius.
- Fix the error-spam loop on malformed input (fail once, don't busy-write).
- If a maintainer can tell me which
pet subcommand/request path is the right one to exercise, I can attach a perf record call graph (I have perf + the binary's debug symbols ready, just need to temporarily relax perf_event_paranoid). The strace above already localizes it to the env-discovery glob scan, but a flamegraph would pin the exact function.
Workaround
Disable / uninstall the ms-python.vscode-python-envs extension, or avoid opening the large workspace, or narrow python.envs.environmentDirectories so the glob doesn't span the whole tree.
Related
Summary
The
petbinary (python-env-tools, shipped byms-python.vscode-python-envs) busy-loops inside a filesystemstat/closescan when handling aconfigureJSON-RPC request whoseenvironmentDirectoriesglob is evaluated against a large workspace. The scan does not complete within the extension's 30-secondconfiguretimeout, so the extension killspet(SIGTERM), restarts it, and re-sendsconfigure— which hangs again. After 3 attempts (~90 s of sustained 100% CPU on one core) the extension gives up.On a thermally-constrained laptop this ~90 s 100% CPU spike pushes the package past 105 °C and the embedded controller cuts power with no OS-level log (no OOM, no panic, no thermal trip in journald — the journal just ends mid-normal-activity). The bug therefore masquerades as a hardware/BIOS issue and is very hard to diagnose. Worth fixing for that reason alone, not just the wasted CPU.
Environment
ms-python.vscode-python-envsv1.20.1 — the latest version compatible with my VS Code. (I have 1.22.0–1.36.0 installed too, but VS Code refuses to load them: "Extension is not compatible with Code 1.108.1. Extension requires: ^1.110.0-20260204". So I can't test newer versions without also upgrading VS Code. Happy to re-test on 1.36.0 if a maintainer confirms whether the scan/timeout behavior changed.)1.108.16.17.0-22-generic, x86_64petbinary: ELF 64-bit, static-pie, with debug_info, not strippedThe trigger workspace
The crash only happens for one of my open workspaces. Its size is the key:
279,488 files / 101 GB, including several
node_modulestrees with symlinks. The other workspaces I have open (346 / ~small files) configure in <1 s and never spin. Theconfigureparams the extension sends for this workspace:{"workspaceDirectories":["/home/<user>/workspace/large-project"], "environmentDirectories":["/home/<user>/workspace/large-project/**/.venv"], "pipenvExecutable":"pipenv","poetryExecutable":"poetry", "cacheDirectory":".../pythonLocator"}Note the
**/.venvglob — pet walks the entire 279k-file tree looking for.venvdirs.Reproduction (CLI, deterministic, ~12 s)
pet serverreads JSON-RPC over stdio withContent-Lengthframing. Sending the exactconfigurerequest the extension sends hangs pet in astat/closescan loop:strace summary (12 s, no
pet find, justconfigure):So pet is in a tight
stat/closeloop walking the workspace, 64% ofstats returning ENOENT (probing for.venv/python/etc. at every entry). 12 s wasn't enough to finish; the extension's 30 s timeout isn't either.Extension log (from the actual crash session)
~/.config/Code/logs/20260829T164624/window2/exthost/ms-python.vscode-python-envs/Python Environments.log:Same pattern reproduces every time this workspace is opened (also seen in the
20260829T213146session). Other workspaces configure in <1 s.Impact / why this matters
petdropped CPU package temp from 100 °C → 90 °C in 2 s. The ~90 s spike pushes the package past 105 °C and the EC cuts power with no OS log. I had repeated unexplained "auto-shutdowns" for weeks before tracing them here.Secondary issue: pet busy-loops on malformed input too
While figuring out the framing I sent a request without a valid
Content-Lengthheader. Instead of failing gracefully,petentered an infinite error-spam loop writing the same error to stderr — 941,762writecalls in 12 s (~78k writes/s), 100% CPU:stderr was the error message recursively nesting itself. A malformed request should error once and continue, not spin. Likely the same lack of backoff/bounding that causes the
configurehang.What I'd suggest (and can help validate)
configurescan: skip / prune common heavy dirs (node_modules,.git,__pycache__,.venvcontents), cap depth or entry count, or make discovery incremental/lazy instead of a full synchronous walk that must finish beforeconfigureresponds.petsubcommand/request path is the right one to exercise, I can attach aperf recordcall graph (I haveperf+ the binary's debug symbols ready, just need to temporarily relaxperf_event_paranoid). The strace above already localizes it to the env-discovery glob scan, but a flamegraph would pin the exact function.Workaround
Disable / uninstall the
ms-python.vscode-python-envsextension, or avoid opening the large workspace, or narrowpython.envs.environmentDirectoriesso the glob doesn't span the whole tree.Related
${workspaceFolder}causing a clean ENOENT failure; this is a hang/spin on a valid (but large) workspace.