From 1003fdbf46657bb3bf22e6cdd66ca7889568a0fb Mon Sep 17 00:00:00 2001 From: mika kuns Date: Tue, 11 Aug 2026 16:55:32 +0200 Subject: [PATCH] fix(worker): name quarantined prompt files and their replacements in the recovery Warn PromptFileRecovery's orphan-quarantine log only reported a bare count, so a customized merge-helper-execute.md silently stopped applying with no pointer to where it went. Log each quarantined file's destination path, and for merge-helper-execute.md specifically call out that it was split into merge-helper-wait.md and merge-helper-merge.md with no content migration. --- .../Lifecycle/PromptFileRecovery.cs | 17 +++++-- .../Lifecycle/PromptFileRecoveryTests.cs | 48 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/ClaudeDo.Worker/Lifecycle/PromptFileRecovery.cs b/src/ClaudeDo.Worker/Lifecycle/PromptFileRecovery.cs index cffa8452..5dae6917 100644 --- a/src/ClaudeDo.Worker/Lifecycle/PromptFileRecovery.cs +++ b/src/ClaudeDo.Worker/Lifecycle/PromptFileRecovery.cs @@ -24,10 +24,21 @@ public sealed class PromptFileRecovery : IHostedService PromptFiles.ReconcileStaleDefaults(_root); var orphans = PromptFiles.QuarantineOrphans(_root); - if (orphans.Count > 0) - _logger.LogWarning("Prompt file recovery: quarantined {Count} orphaned prompt file(s) into _orphans", orphans.Count); - else + if (orphans.Count == 0) + { _logger.LogInformation("Prompt file recovery: no orphaned prompt files found"); + return Task.CompletedTask; + } + + foreach (var orphan_path in orphans) + { + _logger.LogWarning("Prompt file recovery: quarantined orphaned prompt file {orphan_path}", orphan_path); + if (Path.GetFileNameWithoutExtension(orphan_path) + .StartsWith("merge-helper-execute", StringComparison.OrdinalIgnoreCase)) + _logger.LogWarning( + "Prompt file recovery: {orphan_path} was a customization of the retired merge-helper-execute.md prompt, which was split into merge-helper-wait.md and merge-helper-merge.md; its content was not migrated to either", + orphan_path); + } return Task.CompletedTask; } diff --git a/tests/ClaudeDo.Worker.Tests/Lifecycle/PromptFileRecoveryTests.cs b/tests/ClaudeDo.Worker.Tests/Lifecycle/PromptFileRecoveryTests.cs index 0ebf0b52..636b7376 100644 --- a/tests/ClaudeDo.Worker.Tests/Lifecycle/PromptFileRecoveryTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Lifecycle/PromptFileRecoveryTests.cs @@ -1,12 +1,31 @@ using System.Text.Json; using ClaudeDo.Data; using ClaudeDo.Worker.Lifecycle; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace ClaudeDo.Worker.Tests.Lifecycle; public sealed class PromptFileRecoveryTests { + private sealed class CapturingLogger : ILogger + { + public List Messages { get; } = new(); + + public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance; + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) + => Messages.Add(formatter(state, exception)); + + private sealed class NullScope : IDisposable + { + public static readonly NullScope Instance = new(); + public void Dispose() { } + } + } + [Fact] public async Task StartAsync_ReconcilesStaleDefaultAndQuarantinesOrphan_WithoutTouchingRealEdit() { @@ -46,4 +65,33 @@ public sealed class PromptFileRecoveryTests if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } + + [Fact] + public async Task StartAsync_QuarantinesOrphanedMergeHelperExecute_WarnNamesFileAndReplacements() + { + var root = Path.Combine(Path.GetTempPath(), $"claudedo_prompts_{Guid.NewGuid():N}"); + try + { + Directory.CreateDirectory(root); + var orphanPath = Path.Combine(root, "merge-helper-execute.md"); + File.WriteAllText(orphanPath, "my old customized triage+wait+merge prompt"); + + var logger = new CapturingLogger(); + var sut = new PromptFileRecovery(logger, root); + + await sut.StartAsync(CancellationToken.None); + + var dest = Path.Combine(root, "_orphans", "merge-helper-execute.md"); + Assert.False(File.Exists(orphanPath)); + Assert.True(File.Exists(dest)); + + Assert.Contains(logger.Messages, m => m.Contains("merge-helper-execute.md") && m.Contains(dest)); + Assert.Contains(logger.Messages, m => + m.Contains("merge-helper-wait.md") && m.Contains("merge-helper-merge.md")); + } + finally + { + if (Directory.Exists(root)) Directory.Delete(root, recursive: true); + } + } }