diff --git a/Gradata/src/gradata/hooks/session_close.py b/Gradata/src/gradata/hooks/session_close.py index 8e663326..cf0e4970 100644 --- a/Gradata/src/gradata/hooks/session_close.py +++ b/Gradata/src/gradata/hooks/session_close.py @@ -278,9 +278,21 @@ def _run_cloud_sync(brain_dir: str, data: dict) -> None: Claude Code never calls ``brain.end_session()`` directly, so ``_cloud_sync_session`` never fired from IDE sessions before this hook path existed. Gated on GRADATA_API_KEY — no key, no sync, no network. + + As of #194 day 4, Brain.correct() write-through is the default cloud + sync path. The session_close hook tick is redundant when write-through + is active and was double-writing every correction. Set + GRADATA_DISABLE_WRITE_THROUGH=1 to fall back to this legacy path. """ if not os.environ.get("GRADATA_API_KEY"): return + # Default behavior: write-through covers it. Skip the legacy tick. + if os.environ.get("GRADATA_DISABLE_WRITE_THROUGH") != "1": + _log.info( + "write-through enabled, skipping legacy cloud sync from session_close " + "(set GRADATA_DISABLE_WRITE_THROUGH=1 to restore legacy behavior)" + ) + return try: from gradata._core import cloud_sync_tick diff --git a/Gradata/tests/test_session_close_write_through_gate.py b/Gradata/tests/test_session_close_write_through_gate.py new file mode 100644 index 00000000..9add19d9 --- /dev/null +++ b/Gradata/tests/test_session_close_write_through_gate.py @@ -0,0 +1,42 @@ +"""Test the GRADATA_DISABLE_WRITE_THROUGH gate on session_close cloud sync. + +#194 day 4: Brain.correct() write-through is the default cloud sync path. +The session_close hook's legacy cloud_sync_tick is now skipped by default +to avoid double-writes. Only runs when GRADATA_DISABLE_WRITE_THROUGH=1. +""" + +from __future__ import annotations + +from unittest.mock import patch + +from gradata.hooks import session_close + + +def test_run_cloud_sync_skipped_when_write_through_default(monkeypatch): + """Default behavior: write-through covers it, session_close tick is skipped.""" + monkeypatch.setenv("GRADATA_API_KEY", "gd_live_test") + monkeypatch.delenv("GRADATA_DISABLE_WRITE_THROUGH", raising=False) + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + ( + mock_tick.assert_not_called(), + ("session_close must NOT call cloud_sync_tick when write-through is on"), + ) + + +def test_run_cloud_sync_invoked_when_write_through_disabled(monkeypatch): + """Opt-out via GRADATA_DISABLE_WRITE_THROUGH=1: legacy path runs.""" + monkeypatch.setenv("GRADATA_API_KEY", "gd_live_test") + monkeypatch.setenv("GRADATA_DISABLE_WRITE_THROUGH", "1") + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + mock_tick.assert_called_once_with("/tmp/fake-brain", 1) + + +def test_run_cloud_sync_skipped_when_no_api_key(monkeypatch): + """No API key: never call cloud regardless of write-through state.""" + monkeypatch.delenv("GRADATA_API_KEY", raising=False) + monkeypatch.setenv("GRADATA_DISABLE_WRITE_THROUGH", "1") + with patch("gradata._core.cloud_sync_tick") as mock_tick: + session_close._run_cloud_sync("/tmp/fake-brain", {"session_number": 1}) + mock_tick.assert_not_called()