@@ -448,3 +448,85 @@ def test_is_project_path(watch_service, tmp_path):
448448
449449 # Test the project path itself
450450 assert watch_service .is_project_path (project , project_path ) is False
451+
452+
453+ @pytest .mark .asyncio
454+ async def test_handle_changes_skips_deleted_project (
455+ watch_service , project_config , test_project , sync_service , project_service , tmp_path
456+ ):
457+ """Test that handle_changes skips processing changes for projects that have been deleted.
458+
459+ This is a regression test for issue #193 where deleted projects were being recreated
460+ by background sync because the directory still existed on disk.
461+ """
462+ from textwrap import dedent
463+
464+ project_dir = project_config .home
465+
466+ # Create a test file in the project
467+ test_file = project_dir / "test_note.md"
468+ content = dedent ("""
469+ ---
470+ type: knowledge
471+ ---
472+ # Test Note
473+ Test content
474+ """ ).strip ()
475+ await create_test_file (test_file , content )
476+
477+ # Initial sync to create the entity
478+ await sync_service .sync (project_dir )
479+
480+ # Verify entity was created
481+ entity_before = await sync_service .entity_repository .get_by_file_path ("test_note.md" )
482+ assert entity_before is not None
483+
484+ # Create a second project directly in the database and set it as default
485+ # so we can remove the first one (cannot remove default project)
486+ other_project_path = str (tmp_path .parent / "other-project-for-test" )
487+ project_data = {
488+ "name" : "other-project" ,
489+ "path" : other_project_path ,
490+ "permalink" : "other-project" ,
491+ "is_active" : True ,
492+ }
493+ other_project = await project_service .repository .create (project_data )
494+ await project_service .repository .set_as_default (other_project .id )
495+
496+ # Also add to config
497+ config = project_service .config_manager .load_config ()
498+ config .projects ["other-project" ] = other_project_path
499+ config .default_project = "other-project"
500+ project_service .config_manager .save_config (config )
501+
502+ # Remove the test project from configuration (simulating project deletion)
503+ # This should prevent background sync from processing changes
504+ await project_service .remove_project (test_project .name )
505+
506+ # Simulate file changes after project deletion
507+ # These changes should be ignored by the watch service
508+ modified_content = dedent ("""
509+ ---
510+ type: knowledge
511+ ---
512+ # Test Note
513+ Modified content after project deletion
514+ """ ).strip ()
515+ await create_test_file (test_file , modified_content )
516+
517+ changes = {(Change .modified , str (test_file ))}
518+
519+ # Handle changes - should skip processing since project is deleted
520+ await watch_service .handle_changes (test_project , changes )
521+
522+ # Verify that the entity was NOT re-created or updated
523+ # Since the project was deleted, the database should still have the old state
524+ # or the entity should be gone entirely if cleanup happened
525+ entity_after = await sync_service .entity_repository .get_by_file_path ("test_note.md" )
526+
527+ # The entity might be deleted or unchanged, but it should not be updated with new content
528+ if entity_after is not None :
529+ # If the entity still exists, it should have the old content, not the new content
530+ assert entity_after .checksum == entity_before .checksum , (
531+ "Entity should not be updated for deleted project"
532+ )
0 commit comments