Skip to content

Commit fb0b405

Browse files
authored
Harden cache validation and staging OSError handling
1 parent 9ffaf6d commit fb0b405

2 files changed

Lines changed: 17 additions & 5 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6104,8 +6104,12 @@ def _safe_fetch(url: str) -> bytes:
61046104

61056105
# Create steps_base_dir now so the staging temp dir is on the same filesystem,
61066106
# enabling a truly atomic os.rename() below.
6107-
steps_base_dir.mkdir(parents=True, exist_ok=True)
6108-
tmp_path = Path(tempfile.mkdtemp(prefix="speckit_step_tmp_", dir=steps_base_dir))
6107+
try:
6108+
steps_base_dir.mkdir(parents=True, exist_ok=True)
6109+
tmp_path = Path(tempfile.mkdtemp(prefix="speckit_step_tmp_", dir=steps_base_dir))
6110+
except OSError as exc:
6111+
console.print(f"[red]Error:[/red] Failed to create staging directory: {exc}")
6112+
raise typer.Exit(1)
61096113
try:
61106114
try:
61116115
step_yml_content = _safe_fetch(step_yml_url)
@@ -6209,7 +6213,11 @@ def _safe_fetch(url: str) -> bytes:
62096213
# Both paths are under steps_base_dir (same filesystem), so os.rename()
62106214
# is atomic on POSIX and won't leave a partially-written directory at
62116215
# step_dir on failure.
6212-
os.rename(tmp_path, step_dir)
6216+
try:
6217+
os.rename(tmp_path, step_dir)
6218+
except OSError as exc:
6219+
console.print(f"[red]Error:[/red] Failed to install step '{step_id}': {exc}")
6220+
raise typer.Exit(1)
62136221
finally:
62146222
# Clean up if the rename hasn't moved tmp_path yet (i.e. on any failure).
62156223
shutil.rmtree(tmp_path, ignore_errors=True)

src/specify_cli/workflows/catalog.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,9 @@ def _fetch_single_catalog(
924924
if not force_refresh and self._is_url_cache_valid(entry.url):
925925
try:
926926
with open(cache_file, encoding="utf-8") as f:
927-
return json.load(f)
927+
cached = json.load(f)
928+
if isinstance(cached, dict):
929+
return cached
928930
except (json.JSONDecodeError, OSError):
929931
pass
930932

@@ -955,7 +957,9 @@ def _validate_url(url: str) -> None:
955957
if cache_file.exists():
956958
try:
957959
with open(cache_file, encoding="utf-8") as f:
958-
return json.load(f)
960+
cached = json.load(f)
961+
if isinstance(cached, dict):
962+
return cached
959963
except (json.JSONDecodeError, ValueError, OSError):
960964
pass
961965
raise StepCatalogError(

0 commit comments

Comments
 (0)