feat(prompts): retry prompt from file, append only real captured errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-04 14:03:32 +02:00
parent 883dbc6af7
commit edc9f77357
4 changed files with 46 additions and 3 deletions

View File

@@ -2,7 +2,7 @@ using System.Text;
namespace ClaudeDo.Data; namespace ClaudeDo.Data;
public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport } public enum PromptKind { System, Planning, PlanningInitial, Retry, DailyPrep, WeeklyReport, Agent }
public static class PromptFiles public static class PromptFiles
{ {
@@ -16,6 +16,7 @@ public static class PromptFiles
PromptKind.Retry => Path.Combine(Root, "retry.md"), PromptKind.Retry => Path.Combine(Root, "retry.md"),
PromptKind.DailyPrep => Path.Combine(Root, "daily-prep.md"), PromptKind.DailyPrep => Path.Combine(Root, "daily-prep.md"),
PromptKind.WeeklyReport => Path.Combine(Root, "weekly-report.md"), PromptKind.WeeklyReport => Path.Combine(Root, "weekly-report.md"),
PromptKind.Agent => Path.Combine(Root, "agent.md"),
_ => throw new ArgumentOutOfRangeException(nameof(kind)) _ => throw new ArgumentOutOfRangeException(nameof(kind))
}; };
@@ -59,6 +60,7 @@ public static class PromptFiles
PromptKind.Retry => RetryDefault, PromptKind.Retry => RetryDefault,
PromptKind.DailyPrep => DailyPrepDefault, PromptKind.DailyPrep => DailyPrepDefault,
PromptKind.WeeklyReport => WeeklyReportDefault, PromptKind.WeeklyReport => WeeklyReportDefault,
PromptKind.Agent => "",
_ => "" _ => ""
}; };

View File

@@ -6,6 +6,8 @@ namespace ClaudeDo.Worker.Runner;
public sealed class ClaudeProcess : IClaudeProcess public sealed class ClaudeProcess : IClaudeProcess
{ {
public const string NoResultPrefix = "Claude exited with code";
private readonly WorkerConfig _cfg; private readonly WorkerConfig _cfg;
private readonly ILogger<ClaudeProcess> _logger; private readonly ILogger<ClaudeProcess> _logger;
@@ -100,7 +102,7 @@ public sealed class ClaudeProcess : IClaudeProcess
var error = lastStderr.Length > 0 var error = lastStderr.Length > 0
? lastStderr.ToString().Trim() ? lastStderr.ToString().Trim()
: $"Claude exited with code {exitCode} and no result."; : $"{NoResultPrefix} {exitCode} and no result.";
return new RunResult return new RunResult
{ {

View File

@@ -104,7 +104,7 @@ public sealed class TaskRunner
{ {
_logger.LogInformation("Auto-retrying task {TaskId} with session {SessionId}", task.Id, result.SessionId); _logger.LogInformation("Auto-retrying task {TaskId} with session {SessionId}", task.Id, result.SessionId);
var retryConfig = resolvedConfig with { ResumeSessionId = result.SessionId }; var retryConfig = resolvedConfig with { ResumeSessionId = result.SessionId };
var retryPrompt = $"The previous attempt failed with:\n\n{result.ErrorMarkdown}\n\nTry again and fix the issues."; var retryPrompt = BuildRetryPrompt(result.ErrorMarkdown);
var retryResult = await RunOnceAsync(task.Id, task.Title, slot, runDir, retryConfig, 2, true, retryPrompt, ct); var retryResult = await RunOnceAsync(task.Id, task.Title, slot, runDir, retryConfig, 2, true, retryPrompt, ct);
@@ -403,4 +403,14 @@ public sealed class TaskRunner
.Select(p => p!.Trim()); .Select(p => p!.Trim());
return string.Join("\n\n", trimmed); return string.Join("\n\n", trimmed);
} }
public static string BuildRetryPrompt(string? capturedError)
{
var basePrompt = PromptFiles.ReadOrDefault(PromptKind.Retry);
var isReal = !string.IsNullOrWhiteSpace(capturedError)
&& !capturedError!.StartsWith(ClaudeProcess.NoResultPrefix, StringComparison.Ordinal);
return isReal
? $"{basePrompt}\n\nCaptured error from the failed run:\n\n{capturedError!.Trim()}"
: basePrompt;
}
} }

View File

@@ -0,0 +1,29 @@
using ClaudeDo.Worker.Runner;
namespace ClaudeDo.Worker.Tests.Runner;
public class RetryPromptTests
{
[Fact]
public void Generic_no_result_error_is_not_appended()
{
var prompt = TaskRunner.BuildRetryPrompt($"{ClaudeProcess.NoResultPrefix} 1 and no result.");
Assert.DoesNotContain("Captured error", prompt);
Assert.Contains("did not complete", prompt);
}
[Fact]
public void Real_error_is_appended()
{
var prompt = TaskRunner.BuildRetryPrompt("error CS1002: ; expected");
Assert.Contains("Captured error", prompt);
Assert.Contains("CS1002", prompt);
}
[Fact]
public void Null_error_yields_bare_prompt()
{
var prompt = TaskRunner.BuildRetryPrompt(null);
Assert.DoesNotContain("Captured error", prompt);
}
}