diff --git a/src/ClaudeDo.Worker/Online/OnlineSyncService.cs b/src/ClaudeDo.Worker/Online/OnlineSyncService.cs index 091765a..3afb1e5 100644 --- a/src/ClaudeDo.Worker/Online/OnlineSyncService.cs +++ b/src/ClaudeDo.Worker/Online/OnlineSyncService.cs @@ -66,6 +66,12 @@ public sealed class OnlineSyncService : BackgroundService internal async Task TickAsync(CancellationToken ct) { + // Respect a runtime disable. The hosted service stays registered for the process + // lifetime, so toggling the feature off in Settings must stop all sync + auth work + // (incl. OIDC discovery) here — otherwise the loop keeps polling until a restart. + if (!_config.Enabled) + return; + var token = await _auth.GetAccessTokenAsync(ct); if (token is null) { diff --git a/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs b/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs index 15288d6..1edbe5c 100644 --- a/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Online/OnlineSyncServiceTests.cs @@ -55,9 +55,9 @@ public sealed class OnlineSyncServiceTests : IDisposable } } - private OnlineSyncService BuildService(FakeApi api, string? token = "test-token") + private OnlineSyncService BuildService(FakeApi api, string? token = "test-token", bool enabled = true) { - var config = new OnlineInboxConfig { Enabled = true, PollIntervalSeconds = 60 }; + var config = new OnlineInboxConfig { Enabled = enabled, PollIntervalSeconds = 60 }; var auth = new StaticTokenAuthProvider(token); return new OnlineSyncService( _db.CreateFactory(), @@ -209,6 +209,18 @@ public sealed class OnlineSyncServiceTests : IDisposable Assert.Equal(0, api.CallCount); } + [Fact] + public async Task Tick_Disabled_SkipsCycle_NoApiCalls() + { + _ = await SeedAsync(); + var api = new FakeApi(); + var svc = BuildService(api, enabled: false); + + await svc.TickAsync(CancellationToken.None); + + Assert.Equal(0, api.CallCount); + } + // ---- multi-user: owner stamping + guard ---- [Fact]