fix(extensions): reject reinstall when a kept config cannot be read (#3960)

The keep-config rescue branch of install_from_directory() reads each
preserved config with bare read_bytes()/stat() calls, so a kept config
that cannot be read (permission or I/O error) crashed the reinstall
with a raw OSError. The sibling symlink guard four lines above already
rejects with a ValidationError and resolution guidance for the same
reason: bytes that cannot be safely rescued must not reach the rmtree
below.

Wrap the read and raise ValidationError with guidance, while dest_dir
is still untouched so the preserved bytes are never lost.

Assisted-by: GitHub Copilot (model: claude-fable-5, autonomous)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Marsel Safin
2026-08-04 08:01:16 -05:00
committed by GitHub
co-authored by Copilot
parent e9f653318e
commit 0fa86e8e9c
2 changed files with 69 additions and 4 deletions
+18 -4
View File
@@ -2186,10 +2186,24 @@ class ExtensionManager:
"a regular file or remove it — then reinstall."
)
if cfg_file.is_file():
stranded_configs[cfg_file.name] = (
cfg_file.read_bytes(),
cfg_file.stat().st_mode,
)
# A kept config that cannot be read or stat'ed must not
# crash the reinstall with a raw OSError — and must not
# reach the rmtree below unrescued. Like the symlink
# guard above, reject while dest_dir is untouched so the
# preserved bytes are never lost.
try:
stranded_configs[cfg_file.name] = (
cfg_file.read_bytes(),
cfg_file.stat().st_mode,
)
except OSError as exc:
raise ValidationError(
"Preserved extension config for "
f"'{manifest.id}' cannot be read "
f"({cfg_file.name}) in {dest_dir}: {exc}. "
"Resolve manually — fix its permissions or "
"remove it — then reinstall."
) from exc
if stranded_configs and not staging_is_complete:
# Write a durable backup outside dest_dir before any