From dcb7ba61c8723454e2c0aa0a170318ca354b0c34 Mon Sep 17 00:00:00 2001
From: mikemikimike <13286568797@163.com>
Date: Wed, 4 Nov 2026 02:46:44 +0800
Subject: [PATCH 1/3] fix(doctor): read active daemon logs on Windows
---
.../MemoryCurationLlmDoctorCheckTests.cs | 42 +++++++++++++++++++
.../Doctor/MemoryCurationLlmDoctorCheck.cs | 7 +++-
2 files changed, 48 insertions(+), 1 deletion(-)
create mode 100644 src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
new file mode 100644
index 0000000000..6dc122fd2d
--- /dev/null
+++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
@@ -0,0 +1,42 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2026 - 2026 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+using Microsoft.Extensions.Time.Testing;
+using Netclaw.Cli.Doctor;
+using Netclaw.Configuration;
+using Netclaw.Tests.Utilities;
+using Xunit;
+
+namespace Netclaw.Cli.Tests.Doctor;
+
+public sealed class MemoryCurationLlmDoctorCheckTests : IDisposable
+{
+ private readonly DisposableTempDir _temp = new();
+
+ public void Dispose() => _temp.Dispose();
+
+ [Fact]
+ public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append()
+ {
+ var paths = new NetclawPaths(_temp.Path);
+ paths.EnsureDirectoriesExist();
+
+ var logPath = Path.Combine(paths.LogsDirectory, "daemon-2026-05-20.log");
+ using var writer = new StreamWriter(logPath, append: true);
+ writer.WriteLine("curation_llm_decision");
+ writer.WriteLine("curation_llm_timeout");
+ writer.Flush();
+
+ var time = new FakeTimeProvider(
+ new DateTimeOffset(2026, 5, 20, 12, 0, 0, TimeSpan.Zero));
+ var result = await new MemoryCurationLlmDoctorCheck(paths, time)
+ .RunAsync(TestContext.Current.CancellationToken);
+
+ Assert.Equal("Memory Curation LLM", result.Name);
+ Assert.Equal(DoctorSeverity.Warning, result.Severity);
+ Assert.Contains("50%", result.Message);
+ Assert.Contains("(1/2)", result.Message);
+ }
+}
diff --git a/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs b/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs
index 0237fb14ea..d5b7920b61 100644
--- a/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs
+++ b/src/Netclaw.Cli/Doctor/MemoryCurationLlmDoctorCheck.cs
@@ -60,7 +60,12 @@ private async Task RunCoreAsync(CancellationToken cancellatio
continue;
cancellationToken.ThrowIfCancellationRequested();
- using var reader = new StreamReader(file);
+ using var stream = new FileStream(
+ file,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite | FileShare.Delete);
+ using var reader = new StreamReader(stream);
while (await reader.ReadLineAsync(cancellationToken) is { } line)
{
// Failure markers first: none of them contains the success
From e7d075b7a8e1d9cbadf67b11970dfadedc56a544 Mon Sep 17 00:00:00 2001
From: Aaron Stannard
Date: Thu, 5 Nov 2026 20:38:07 +0000
Subject: [PATCH 2/3] test(doctor): do not depend on the culture's percent
format
The message formats the failure rate with the current culture. CI wrote
"50 %" and the test expected "50%", so the test failed on Ubuntu. The
test now checks the severity and the counts "(1/2)", which prove that
doctor read and classified the open log.
---
.../Doctor/MemoryCurationLlmDoctorCheckTests.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
index 6dc122fd2d..8a4376ce6f 100644
--- a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
+++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
@@ -36,7 +36,8 @@ public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append
Assert.Equal("Memory Curation LLM", result.Name);
Assert.Equal(DoctorSeverity.Warning, result.Severity);
- Assert.Contains("50%", result.Message);
+ // The percent text follows the current culture ("50%" or "50 %"), so
+ // check the counts, which do not change with the culture.
Assert.Contains("(1/2)", result.Message);
}
}
From 3236191621024d8d5d41cf88f96ee3c6e3a72643 Mon Sep 17 00:00:00 2001
From: mikemikimike <13286568797@163.com>
Date: Fri, 6 Nov 2026 12:59:26 +0800
Subject: [PATCH 3/3] test(doctor): allow shared log writer in regression test
---
.../Doctor/MemoryCurationLlmDoctorCheckTests.cs | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
index 8a4376ce6f..220d3b8a8e 100644
--- a/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
+++ b/src/Netclaw.Cli.Tests/Doctor/MemoryCurationLlmDoctorCheckTests.cs
@@ -24,7 +24,12 @@ public async Task Reads_and_classifies_log_while_daemon_keeps_it_open_for_append
paths.EnsureDirectoriesExist();
var logPath = Path.Combine(paths.LogsDirectory, "daemon-2026-05-20.log");
- using var writer = new StreamWriter(logPath, append: true);
+ using var writerStream = new FileStream(
+ logPath,
+ FileMode.Append,
+ FileAccess.Write,
+ FileShare.ReadWrite | FileShare.Delete);
+ using var writer = new StreamWriter(writerStream);
writer.WriteLine("curation_llm_decision");
writer.WriteLine("curation_llm_timeout");
writer.Flush();