Ignore removed Time Travel workspaces

Drop queued debounced snapshots when the workspace directory is gone before the timer flushes. This keeps transient delete/rename races from surfacing as Time Travel snapshot errors while preserving normal snapshot failures for existing workspaces.

Alessandro committed Jul 9, 2026 at 17:26 UTC 1a6d0eb6147edb763370b6d1554e71441f5644a1
2 files changed +25
plugins/_time_travel/helpers/time_travel.py
+2
@@ -618,6 +618,8 @@ def _flush_debounced_snapshot(workspace_id: str) -> None:
618
619 try:
620 workspace = payload["workspace"]
621 + if not workspace.real_path.is_dir():
622 + return
623 TimeTravelService(workspace).snapshot(
624 trigger=str(payload.get("trigger") or "watchdog"),
625 metadata=payload.get("metadata") or {},
tests/test_time_travel.py
+23
@@ -351,6 +351,29 @@ def test_debounced_snapshots_coalesce_to_one_commit(workspace):
351 tt.clear_debounced_snapshots()
352
353
354 +def test_debounced_snapshot_skips_removed_workspace(workspace, monkeypatch):
355 + root, service = workspace
356 + errors = []
357 + monkeypatch.setattr(tt.PrintStyle, "error", lambda message: errors.append(message))
358 + tt.clear_debounced_snapshots()
359 + try:
360 + (root / "file.txt").write_text("one\n", encoding="utf-8")
361 + tt.schedule_debounced_snapshot(
362 + service.workspace,
363 + trigger="watchdog",
364 + metadata={"source": "watchdog"},
365 + delay=60,
366 + )
367 + shutil.rmtree(root)
368 +
369 + tt.flush_debounced_snapshots()
370 +
371 + assert errors == []
372 + assert not service.workspace.repo_git_path.exists()
373 + finally:
374 + tt.clear_debounced_snapshots()
375 +
376 +
377 def test_workspace_resolution_prefers_project_and_rejects_external_paths(monkeypatch: pytest.MonkeyPatch, workspace):
378 root, _service = workspace
379 projects_mod = ModuleType("helpers.projects")