Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions agent_fox/engine/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,23 @@ def _barrier_sync(infra: dict[str, Any], config: Any) -> None:
)
except Exception:
logger.warning("Barrier ingestion failed", exc_info=True)
finally:
infra["_barrier_ingestion_ran"] = True


def _cleanup_infrastructure(infra: dict[str, Any], config: Any) -> None:
"""Clean up infrastructure resources."""
knowledge_db = infra["knowledge_db"]

# Re-ingest to capture new commits/ADRs
try:
run_background_ingestion(
knowledge_db.connection,
config.knowledge,
Path.cwd(),
)
except Exception:
logger.warning("Final ingestion failed", exc_info=True)
if not infra.get("_barrier_ingestion_ran", False):
try:
run_background_ingestion(
knowledge_db.connection,
config.knowledge,
Path.cwd(),
)
except Exception:
logger.warning("Final ingestion failed", exc_info=True)

# Close sinks and DB
try:
Expand Down
59 changes: 59 additions & 0 deletions tests/unit/engine/test_double_ingestion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Regression tests for double git ingestion at end-of-run (issue #505).

Verifies that _cleanup_infrastructure skips run_background_ingestion when
a sync barrier already ran ingestion during the same engine run.
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture
def _mock_ingestion():
"""Patch run_background_ingestion and return the mock."""
with patch("agent_fox.engine.run.run_background_ingestion") as mock:
yield mock


class TestCleanupSkipsIngestionAfterBarrier:
"""_cleanup_infrastructure must not re-ingest when a barrier already did."""

def test_cleanup_skips_ingestion_after_barrier(self, _mock_ingestion: MagicMock) -> None:
from agent_fox.engine.run import _barrier_sync, _cleanup_infrastructure

config = MagicMock()
infra = {"knowledge_db": MagicMock(), "sink_dispatcher": MagicMock()}

_barrier_sync(infra, config)
assert _mock_ingestion.call_count == 1

_mock_ingestion.reset_mock()
_cleanup_infrastructure(infra, config)
_mock_ingestion.assert_not_called()

def test_cleanup_ingests_when_no_barrier_ran(self, _mock_ingestion: MagicMock) -> None:
from agent_fox.engine.run import _cleanup_infrastructure

config = MagicMock()
infra = {"knowledge_db": MagicMock(), "sink_dispatcher": MagicMock()}

_cleanup_infrastructure(infra, config)
_mock_ingestion.assert_called_once()

def test_barrier_failure_still_sets_flag(self, _mock_ingestion: MagicMock) -> None:
from agent_fox.engine.run import _barrier_sync, _cleanup_infrastructure

_mock_ingestion.side_effect = RuntimeError("ingestion failed")

config = MagicMock()
infra = {"knowledge_db": MagicMock(), "sink_dispatcher": MagicMock()}

_barrier_sync(infra, config)

_mock_ingestion.reset_mock()
_mock_ingestion.side_effect = None
_cleanup_infrastructure(infra, config)
_mock_ingestion.assert_not_called()