You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 1 — Foundation (parallelizable, no behavior change)
PR 1 — Cache key hash utility — Pure functions for dependency-list normalization (sort, lowercase, strip whitespace) and SHA-256 truncation. Unit tests only. Depends on: —
PR 2 — Cache layout + meta.json sidecar helpers — Resolve <globalStorage>/script-envs-v1/<hash>; typed MetaJson interface; atomic read/write; pure TTL-eviction helper (given a list of entries, return the paths to delete). Depends on: —
PR 3 — requires-python → interpreter selection — Filter api.getEnvironments('global') via matchesPythonVersion; extract a lower-bound version (">=3.13" → "3.13") for the uv-install fallback. Depends on: —
All three can be developed in parallel.
Phase 2 — Manager (internal only)
PR 4 — InlineScriptEnvManager skeleton — Class implementing EnvironmentManager; displayName = "Inline script environments"; registered in extension.ts. getEnvironments returns []. No create / set yet. Smoke check: appears as an empty section in the picker. Depends on: —
PR 5 — create() happy path — Given (scriptUri, metadata): pick a compatible installed Python (PR 3), compute the hash (PR 1), build via existing createWithProgress, install dependencies, and write meta.json (PR 2). No persistence. No uv-install fallback. Depends on: 1, 2, 3, 4
PR 6 — create() uv-install fallback — Extend the promptInstallPythonViaUv trigger union with 'inlineScript'; thread the requires-python lower bound to installPythonWithUv(version); wire it into create() for the no-compatible-interpreter case. Depends on: 3, 5
PR 7 — Persistence: get / set + Memento — New INLINE_SCRIPT_ENVS_KEY; per-URI fsPathToEnv map; cache-hit re-verification of requires-python (Q4 step 3). Mirrors the VenvManager pattern. Depends on: 4
PR 8 — Activation-time discovery — Walk the cache directory, load sidecars, resolve through nativeFinder.resolve(), and register items in the manager. Defer via setImmediate so activation is not blocked. Depends on: 2, 4, 7
After Phase 2, the manager is fully functional but nothing automatically uses it.
Phase 3 — Routing (the "go-live" PRs)
PR 9 — Route PEP 723 scripts to the inline manager — In envManagers.getEnvironmentManager(uri), return the inline manager when uri is a known PEP 723 script and a validated cached environment exists; otherwise fall through to normal routing. Use lazy parsing with per-URI memoization. Depends on: 4, 7
PR 10 — Per-script project registration — When an inline environment is created or set, register the script as an exact pythonProjects[] entry so routing and per-file events survive restart. Cleanup removes only the generated inline-script entry. Depends on: 9
These PRs live in other repositories (microsoft/pyrx for Pylance and microsoft/vscode-python for the debugger fix). They are required for the feature to work end-to-end. They can be developed in parallel with Phases 1–3, but must land before Phase 4 ships so the user-facing setup action activates the correct language-service and debugging behavior.
PR 17 — [microsoft/pyrx] Per-file pythonPath lookup for .py files — Extend documentWorkspaceResolver.getWorkspaceForFile to query the per-file Python path through the existing workspace/configuration request, mirroring the notebook-cell path. When the per-file path differs from the workspace path, create an immutable sub-workspace pinned to that interpreter; when it matches, retain the existing workspace with no additional cost. Depends on: 7
PR 18 — [microsoft/pyrx] Per-file environment-change notification + handler — Add python/didChangeFilePythonPath; re-resolve the file's Python path, move the file between workspaces, invalidate/reanalyze it, and dispose the old workspace when empty. Depends on: 10, 17
PR 19 — [microsoft/vscode-python] Debug resolver per-file environment lookup fix — In resolveAndUpdatePythonPath, prefer the program URI for getActiveInterpreter when present, falling back to the workspace folder. Apply the same shape to both the pythonPath and python branches. This is a small fix (approximately 10 lines) for a pre-existing per-file scope gap. Depends on: 7
Run Python File (the green triangle) already routes per-file through codeExecutionManager → runInTerminal → getEnvironment(fileUri), so it needs no cross-repository work.
Phase 4 — UX (the user-facing entry points)
PR 11 — CodeLens: Set up environment for this script — Show a CodeLens above the # /// script block only when saved PEP 723 metadata has no matching inline environment. Clicking it creates or reuses the environment, registers the exact script project (PR 10), persists the association, and publishes the per-file environment change. Hide it while the association is current; show it again after metadata invalidates the association. Depends on: 5, 9, 10
PR 12 — Bulk command: Set Up Environments for Inline Script Files — workspace.findFiles('**/*.py', exclude) → parse/filter → multi-select quick pick → run the same create/register/persist flow as PR 11 for each selected script. Cap results; exclude .venv and node_modules. Depends on: 5, 9, 10
Phase 5 — Lifecycle, telemetry, and polish
PR 13 — Command: Clear Script Environment Cache — Modal confirmation; delete the cache bucket; clear Memento; remove generated pythonProjects[] entries; fire onDidChangeEnvironment. Reuse the existing venv-removal safety guards. Depends on: 2, 7
PR 14 — Opportunistic TTL eviction — Once per session, walk the cache during environment creation/reuse and delete environments whose lastUsedAt is older than 14 days. Reuse PR 13 cleanup helpers. Depends on: 2, 7, 13
PR 15 — Remaining inlineScript.* telemetry — envCreated, envReuseHit, and envError, including the 'compatible-python-declined' category. Add typed GDPR schema entries and call sites. Depends on: 5, 6, 7
PR 16 — Status-bar decision: no special treatment (Option A) — Before setup, keep showing the current workspace/default environment. PR 11's CodeLens provides discoverability. After setup, the existing active-document environment lookup naturally shows Python X.Y (inline). When metadata becomes stale, routing falls back and the CodeLens returns. No separate implementation PR is required.
Hashing is pure crypto; meta.json is filesystem + globalStorageUri integration. Different review areas.
5 vs 6
The happy path stays in the inline manager; fallback touches uvPythonInstaller.ts and changes the trigger union.
7 separate from 5
Persistence is the easiest place to introduce regressions; isolating it makes bisecting easier.
9 vs 10
Routing is read-only; project registration writes settings. Different reversal costs.
11 vs 12
Single-script CodeLens work runs on editor/document changes; bulk setup is a one-shot workspace scan. Different performance and UX concerns.
13 vs 14
Clear-cache is user-triggered and destructive; TTL is silent and opportunistic. Different telemetry and user-trust profiles.
17 vs 18
Open-time per-file lookup is read-only; change-time rerouting moves files and forces reanalysis.
19 vs 17/18
Different repositories, teams, and release cycles. The debugger fix is self-contained and helps all per-file interpreter users.
First-rollout UX decision
Use a CodeLens as the only single-script setup surface:
Set up environment for this script
# /// script
# dependencies = ["requests"]
# ///
Before setup, the status bar remains unchanged and shows the current workspace/default environment.
Clicking the CodeLens creates or reuses the inline environment, registers the exact script project, persists the association, and publishes the active-environment change.
After setup, the existing status-bar lookup naturally shows the inline environment.
If setup fails or is cancelled, the current environment stays active and the CodeLens remains.
If saved metadata later invalidates the association, routing falls back and the CodeLens returns.
Do not add a special Select Interpreter setup item or a separate status-bar implementation for the first rollout.
Behavioral cut-over
PR 9 is the only PR in this repository that changes implicit routing behavior. Keep it behind python-envs.inlineScripts.enabled until the user-facing rollout is intentional. Cross-repository PRs 17–19 are silent no-ops for users without a per-file environment registration.
See #1601 for the design document.
PR phases at a glance
Phase 1 — Foundation (parallelizable, no behavior change)
meta.jsonsidecar helpers — Resolve<globalStorage>/script-envs-v1/<hash>; typedMetaJsoninterface; atomic read/write; pure TTL-eviction helper (given a list of entries, return the paths to delete). Depends on: —requires-python→ interpreter selection — Filterapi.getEnvironments('global')viamatchesPythonVersion; extract a lower-bound version (">=3.13"→"3.13") for the uv-install fallback. Depends on: —Phase 2 — Manager (internal only)
InlineScriptEnvManagerskeleton — Class implementingEnvironmentManager;displayName = "Inline script environments"; registered inextension.ts.getEnvironmentsreturns[]. Nocreate/setyet. Smoke check: appears as an empty section in the picker. Depends on: —create()happy path — Given(scriptUri, metadata): pick a compatible installed Python (PR 3), compute the hash (PR 1), build via existingcreateWithProgress, install dependencies, and writemeta.json(PR 2). No persistence. No uv-install fallback. Depends on: 1, 2, 3, 4create()uv-install fallback — Extend thepromptInstallPythonViaUvtrigger union with'inlineScript'; thread therequires-pythonlower bound toinstallPythonWithUv(version); wire it intocreate()for the no-compatible-interpreter case. Depends on: 3, 5get/set+ Memento — NewINLINE_SCRIPT_ENVS_KEY; per-URIfsPathToEnvmap; cache-hit re-verification ofrequires-python(Q4 step 3). Mirrors theVenvManagerpattern. Depends on: 4nativeFinder.resolve(), and register items in the manager. Defer viasetImmediateso activation is not blocked. Depends on: 2, 4, 7Phase 3 — Routing (the "go-live" PRs)
envManagers.getEnvironmentManager(uri), return the inline manager whenuriis a known PEP 723 script and a validated cached environment exists; otherwise fall through to normal routing. Use lazy parsing with per-URI memoization. Depends on: 4, 7pythonProjects[]entry so routing and per-file events survive restart. Cleanup removes only the generated inline-script entry. Depends on: 9Phase 3.5 — Cross-repository integration (Pylance + Python extension)
These PRs live in other repositories (
microsoft/pyrxfor Pylance andmicrosoft/vscode-pythonfor the debugger fix). They are required for the feature to work end-to-end. They can be developed in parallel with Phases 1–3, but must land before Phase 4 ships so the user-facing setup action activates the correct language-service and debugging behavior.microsoft/pyrx] Per-filepythonPathlookup for.pyfiles — ExtenddocumentWorkspaceResolver.getWorkspaceForFileto query the per-file Python path through the existingworkspace/configurationrequest, mirroring the notebook-cell path. When the per-file path differs from the workspace path, create an immutable sub-workspace pinned to that interpreter; when it matches, retain the existing workspace with no additional cost. Depends on: 7microsoft/pyrx] Per-file environment-change notification + handler — Addpython/didChangeFilePythonPath; re-resolve the file's Python path, move the file between workspaces, invalidate/reanalyze it, and dispose the old workspace when empty. Depends on: 10, 17microsoft/vscode-python] Debug resolver per-file environment lookup fix — InresolveAndUpdatePythonPath, prefer the program URI forgetActiveInterpreterwhen present, falling back to the workspace folder. Apply the same shape to both thepythonPathandpythonbranches. This is a small fix (approximately 10 lines) for a pre-existing per-file scope gap. Depends on: 7Phase 4 — UX (the user-facing entry points)
# /// scriptblock only when saved PEP 723 metadata has no matching inline environment. Clicking it creates or reuses the environment, registers the exact script project (PR 10), persists the association, and publishes the per-file environment change. Hide it while the association is current; show it again after metadata invalidates the association. Depends on: 5, 9, 10workspace.findFiles('**/*.py', exclude)→ parse/filter → multi-select quick pick → run the same create/register/persist flow as PR 11 for each selected script. Cap results; exclude.venvandnode_modules. Depends on: 5, 9, 10Phase 5 — Lifecycle, telemetry, and polish
pythonProjects[]entries; fireonDidChangeEnvironment. Reuse the existing venv-removal safety guards. Depends on: 2, 7lastUsedAtis older than 14 days. Reuse PR 13 cleanup helpers. Depends on: 2, 7, 13inlineScript.*telemetry —envCreated,envReuseHit, andenvError, including the'compatible-python-declined'category. Add typed GDPR schema entries and call sites. Depends on: 5, 6, 7Python X.Y (inline). When metadata becomes stale, routing falls back and the CodeLens returns. No separate implementation PR is required.Dependency summary
Why these seams
meta.jsonis filesystem +globalStorageUriintegration. Different review areas.uvPythonInstaller.tsand changes the trigger union.First-rollout UX decision
Use a CodeLens as the only single-script setup surface:
Behavioral cut-over
PR 9 is the only PR in this repository that changes implicit routing behavior. Keep it behind
python-envs.inlineScripts.enableduntil the user-facing rollout is intentional. Cross-repository PRs 17–19 are silent no-ops for users without a per-file environment registration.