Repository navigation
[ty] Invalidate dependency metadata when environments change - #29126
Conversation
Recompute dependency metadata when the resolved Python environment or uv's tracked environment directory changes. Resolve symlink targets on each query execution to avoid caching an outdated canonical path. Keep failed environment selections in a Result while retaining the last usable environment for watching and recovery. Fixes astral-sh/ty#4604
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 98.24%. The percentage of expected errors that received a diagnostic held steady at 98.24%. The number of fully passing files held steady at 134/146. |
Memory usage reportMemory usage unchanged ✅ |
|
|
| .map_err(|error| DependencyMetadataError::InvalidEnvironment { | ||
| path: environment.to_path_buf(), | ||
| message: error.to_string().into(), | ||
| })?; |
There was a problem hiding this comment.
If .venv is a symlink to a directory named environment, deleting and recreating the environment directory can leave dependency metadata permanently stuck at InvalidEnvironment. Deleting and re-creating environment may not produce events for the unchanged .venv symlink; we need to be able to recover from the directory events alone, even when uv metadata hasn't changed.
After deletion, canonicalizing .venv fails and returns here before the query reads either the tracked status of environment or program_settings. Salsa drops those dependencies. Recreating environment then does not invalidate the cached error, even when program settings are refreshed successfully. The warning persists and dependency checks stay disabled.
On main, this sequence keeps returning cached success throughout deletion and recreation: it never notices the missing directory in the first place. An initially cached InvalidEnvironment could already remain stale, too. What is new here is that deleting a previously working environment now invalidates success, but recreating it leaves us stuck with the error.
I reproduced this by appending the following to dependency_metadata_tracks_environment_symlink, after its final deletion assertion:
std::fs::create_dir_all(environment.join("lib/python3.13/site-packages"))?;
std::fs::write(
environment.join("pyvenv.cfg"),
"home = /missing\nversion = 3.13.0\ninclude-system-site-packages = false",
)?;
File::sync_path(&mut db, &environment);
assert_matches!(project.dependency_metadata(&db), Ok(Some(_)));The final assertion still gets InvalidEnvironment.
I think the safest fix is probably to call report_untracked_read() on the canonicalization error path?
.map_err(|error| {
db.report_untracked_read();
DependencyMetadataError::InvalidEnvironment {
path: environment.to_path_buf(),
message: error.to_string().into(),
}
})?;This makes Salsa retry the failed query in the next database revision, regardless of whether any dependencies have changed. Just preserving the dependency on program_settings isn't good enough, since then we only recover if the settings actually change.
Successful queries would still keep their normal selective invalidation; we would just keep retrying this canonicalization on every revision (even unrelated revisions) as long as the error persists. But this seems like probably the right behavior, since with a broken environment probably everything else is broken, too.
I verified this fixes direct directory synchronization, normal apply_changes delete/create events, and the case where ty selects a different environment from uv and its program settings stay unchanged. All 80 ty_project unit tests, including those three recovery cases, pass with this change.
There was a problem hiding this comment.
Fantastic catch! I think the suggested solution introduces a few edge cases, but none that I can see occuring during normal development, for example:
In a setup where uv's environment is a double symlink: .venv -> bridge/env -> actual_env, and ty's is directly actual_env, then the following sequence of events:
- check a python file that produces a
missing-direct-dependencydiagnostic for a dependency insideenvironment - make
bridgeinaccessible bychmoding it -> this causes canonicalizing of uv's path to fail, but ty can still resolve the dependency throughactual_env; however,missing-direct-dependencyis turned off because the canonicalization failure - restore
bridgepermissions; with the proposed solution, we'll retry and obtain dependency metadata even though.venvhasn't changed, so we'll use the cached import check which produced no diagnostic in step 2 - create an empty
ty.tomland rediscover the project -> this causes a configuration change but the dependency metadata would not change, leading to still no diagnostics.
I think all of these edge cases are too contrived to warrant the complexity of solving them, so I'll just go with your suggestion which definitely improves things in (comparatively) normal cases.
bf133ff to
c447a6e
Compare
Project::dependency_metadataresolves Python environments inside a Salsa query without tracking those inputs. This can leave invalid-environment, missing-environment, and environment-mismatch diagnostics stale after an environment changes.Store the selected environment's resolution result in
ProgramSettingsand track uv's environment directory through Salsa. Resolve uv's canonical path whenever the query runs so it observes symlink changes even when uv metadata is unchanged.After a failed selection, retain the last usable environment for watching and recovery, and preserve the checking program's search paths. Dependency metadata reports the current environment error.
Fixes astral-sh/ty#4604.