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:
@@ -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 => "",
|
||||||
_ => ""
|
_ => ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
tests/ClaudeDo.Worker.Tests/Runner/RetryPromptTests.cs
Normal file
29
tests/ClaudeDo.Worker.Tests/Runner/RetryPromptTests.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user