Skip to content

Cross-session cache for discovered system Python path#1455

Draft
eleanorjboyd wants to merge 2 commits intomicrosoft:mainfrom
eleanorjboyd:electrical-mongoose
Draft

Cross-session cache for discovered system Python path#1455
eleanorjboyd wants to merge 2 commits intomicrosoft:mainfrom
eleanorjboyd:electrical-mongoose

Conversation

@eleanorjboyd
Copy link
Copy Markdown
Member

@eleanorjboyd eleanorjboyd commented Apr 14, 2026

Persist the discovered system Python path (e.g., /usr/bin/python3 or C:\Python312\python.exe) to globalState. On next startup, use the cached path immediately and verify it's still valid in the background.

Potential risk: The cache could become stale if the user uninstalls/upgrades Python. Background verification mitigates this — the cached env is used immediately, and if verification fails, a re-scan is triggered. The user would see a brief env switch in the rare case the cache is wrong.

@eleanorjboyd eleanorjboyd changed the title Electrical mongoose Cross-session cache for discovered system Python path Apr 14, 2026
@eleanorjboyd eleanorjboyd reopened this Apr 14, 2026
@eleanorjboyd eleanorjboyd requested a review from Copilot April 14, 2026 21:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds a cross-session “fast path” for resolving the system Python by persisting the discovered global Python path and using it immediately on next startup, while verifying validity in the background.

Changes:

  • Add optional global-scope persisted-path support to tryFastPathGet() and emit telemetry for global cache hit/miss/stale.
  • Update system Python manager/cache to read/write the global persisted path via global persistent state.
  • Extend unit tests to cover global-scope fast-path behavior and telemetry emission.
Show a summary per file
File Description
src/test/managers/fastPath.get.unit.test.ts Stubs global cache getter to avoid test behavior changes with new global fast-path support.
src/test/managers/common/fastPath.unit.test.ts Adds test coverage for global persisted-path behavior and verifies telemetry results.
src/managers/common/fastPath.ts Implements optional global persisted-path lookup and emits global cache telemetry.
src/managers/builtin/sysPythonManager.ts Wires system manager into new global persisted-path fast-path option.
src/managers/builtin/cache.ts Moves global key storage to global persistent state and clears workspace/global keys in their respective stores.
src/common/telemetry/constants.ts Adds GLOBAL_ENV_CACHE event and GDPR property mapping for the new telemetry.

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 2

Comment on lines +93 to 100
// Look up the persisted path — either from workspace cache or global cache
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));

// Track cross-session cache performance for global scope
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stopwatch is started after awaiting the persisted-path lookup, so the emitted <duration> excludes time spent reading the global cache (and will also be near-zero for miss). If the intent is to measure end-to-end global fast-path latency, start timing before the lookup (or rename/comment to clarify that the duration is resolve-only).

Suggested change
// Look up the persisted path — either from workspace cache or global cache
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));
// Track cross-session cache performance for global scope
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;
// Track end-to-end cross-session cache performance for global scope, including persisted-path lookup.
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;
// Look up the persisted path — either from workspace cache or global cache
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));

Copilot uses AI. Check for mistakes.
Comment on lines +94 to 139
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));

// Track cross-session cache performance for global scope
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;

if (persistedPath) {
try {
const resolved = await opts.resolve(persistedPath);
if (resolved) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'hit',
});
}
return { env: resolved };
}
// Cached path found but resolve returned undefined (e.g., Python was uninstalled)
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
} catch (err) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
traceWarn(
`[${opts.label}] Fast path resolve failed for '${persistedPath}', falling back to full init:`,
err,
);
}
} else {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'miss',
});
}
traceVerbose(`[${opts.label}] Fast path: no persisted path, falling through to slow path`);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using non-null assertions (opts.getGlobalPersistedPath!() and opts.scope as Uri) makes control-flow assumptions less explicit. Consider branching on isGlobalScope and assigning strongly-typed locals (e.g., const getGlobalPersistedPath = opts.getGlobalPersistedPath; / const scope = opts.scope as Uri) to avoid repeated assertions and make the two code paths clearer.

Suggested change
const persistedPath = isGlobalScope
? await opts.getGlobalPersistedPath!()
: await opts.getPersistedPath(opts.getProjectFsPath(opts.scope as Uri));
// Track cross-session cache performance for global scope
const cacheStopWatch = isGlobalScope ? new StopWatch() : undefined;
if (persistedPath) {
try {
const resolved = await opts.resolve(persistedPath);
if (resolved) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'hit',
});
}
return { env: resolved };
}
// Cached path found but resolve returned undefined (e.g., Python was uninstalled)
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
} catch (err) {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
}
traceWarn(
`[${opts.label}] Fast path resolve failed for '${persistedPath}', falling back to full init:`,
err,
);
}
} else {
if (isGlobalScope) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch!.elapsedTime, {
managerLabel: opts.label,
result: 'miss',
});
}
traceVerbose(`[${opts.label}] Fast path: no persisted path, falling through to slow path`);
if (isGlobalScope) {
const getGlobalPersistedPath = opts.getGlobalPersistedPath;
if (!getGlobalPersistedPath) {
return undefined;
}
const cacheStopWatch = new StopWatch();
const persistedPath = await getGlobalPersistedPath();
if (persistedPath) {
try {
const resolved = await opts.resolve(persistedPath);
if (resolved) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch.elapsedTime, {
managerLabel: opts.label,
result: 'hit',
});
return { env: resolved };
}
// Cached path found but resolve returned undefined (e.g., Python was uninstalled)
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
} catch (err) {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch.elapsedTime, {
managerLabel: opts.label,
result: 'stale',
});
traceWarn(
`[${opts.label}] Fast path resolve failed for '${persistedPath}', falling back to full init:`,
err,
);
}
} else {
sendTelemetryEvent(EventNames.GLOBAL_ENV_CACHE, cacheStopWatch.elapsedTime, {
managerLabel: opts.label,
result: 'miss',
});
traceVerbose(`[${opts.label}] Fast path: no persisted path, falling through to slow path`);
}
} else {
const scope = opts.scope as Uri;
const persistedPath = await opts.getPersistedPath(opts.getProjectFsPath(scope));
if (persistedPath) {
try {
const resolved = await opts.resolve(persistedPath);
if (resolved) {
return { env: resolved };
}
} catch (err) {
traceWarn(
`[${opts.label}] Fast path resolve failed for '${persistedPath}', falling back to full init:`,
err,
);
}
} else {
traceVerbose(`[${opts.label}] Fast path: no persisted path, falling through to slow path`);
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants