From a8d8a8bd65c7a78e662a7c6cde5144029c1c4836 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Wed, 3 Jun 2026 10:22:06 +0200 Subject: [PATCH] fix(worker): sanitize report model arg, fix multi-repo summary attribution and standup-weekday sentinel --- .../ViewModels/Modals/SettingsModalViewModel.cs | 2 +- src/ClaudeDo.Worker/Hub/WorkerHub.cs | 2 +- .../Report/ClaudeHistoryReader.cs | 8 ++++++++ src/ClaudeDo.Worker/Report/WeekReportService.cs | 6 +++++- .../Report/ClaudeHistoryReaderTests.cs | 16 ++++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs index 73def14..d6974af 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/SettingsModalViewModel.cs @@ -52,7 +52,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase ? @"C:\Private" : string.Join(Environment.NewLine, System.Text.Json.JsonSerializer.Deserialize>(dto.ReportExcludedPaths) ?? new()); - General.StandupWeekday = dto.StandupWeekday == 0 ? (int)DayOfWeek.Wednesday : dto.StandupWeekday; + General.StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday; } else StatusMessage = "Worker offline — settings read-only."; diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs index d019111..9a07fe3 100644 --- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs +++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs @@ -237,7 +237,7 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub WorktreeAutoCleanupEnabled = dto.WorktreeAutoCleanupEnabled, WorktreeAutoCleanupDays = dto.WorktreeAutoCleanupDays, ReportExcludedPaths = dto.ReportExcludedPaths, - StandupWeekday = dto.StandupWeekday == 0 ? (int)DayOfWeek.Wednesday : dto.StandupWeekday, + StandupWeekday = dto.StandupWeekday is >= 0 and <= 6 ? dto.StandupWeekday : (int)DayOfWeek.Wednesday, }); } diff --git a/src/ClaudeDo.Worker/Report/ClaudeHistoryReader.cs b/src/ClaudeDo.Worker/Report/ClaudeHistoryReader.cs index 84e52ea..f5e3c31 100644 --- a/src/ClaudeDo.Worker/Report/ClaudeHistoryReader.cs +++ b/src/ClaudeDo.Worker/Report/ClaudeHistoryReader.cs @@ -79,6 +79,14 @@ public sealed class ClaudeHistoryReader : IClaudeHistoryReader } else { + // Keep only the closing summary per (repo, day). If this turn moved to a + // different repo/day (e.g. the session cd'd), flush the previous one first. + if (lastAssistantText is not null && + (cwd != lastAssistantRepo || date != lastAssistantDate)) + { + Bucket(buckets, lastAssistantRepo!, lastAssistantDate).Summaries.Add(lastAssistantText); + lastAssistantText = null; + } lastAssistantText = text.Trim(); lastAssistantRepo = cwd; lastAssistantDate = date; diff --git a/src/ClaudeDo.Worker/Report/WeekReportService.cs b/src/ClaudeDo.Worker/Report/WeekReportService.cs index 1727c0b..75e45f8 100644 --- a/src/ClaudeDo.Worker/Report/WeekReportService.cs +++ b/src/ClaudeDo.Worker/Report/WeekReportService.cs @@ -65,7 +65,11 @@ public sealed class WeekReportService : IWeekReportService else { var prompt = WeekReportPromptBuilder.Build(start, end, activity, notesByDay); - var args = $"-p --output-format stream-json --verbose --permission-mode auto --model {model}"; + // Guard against argument injection via the model setting: model aliases/ids are + // alphanumerics, dashes and dots only. + var safeModel = new string(model.Where(c => char.IsLetterOrDigit(c) || c is '-' or '.').ToArray()); + if (safeModel.Length == 0) safeModel = "sonnet"; + var args = $"-p --output-format stream-json --verbose --permission-mode auto --model {safeModel}"; var result = await _claude.RunAsync(args, prompt, Path.GetTempPath(), _ => Task.CompletedTask, ct); if (!result.IsSuccess) throw new InvalidOperationException(result.ErrorMarkdown ?? "Claude konnte den Bericht nicht erzeugen."); diff --git a/tests/ClaudeDo.Worker.Tests/Report/ClaudeHistoryReaderTests.cs b/tests/ClaudeDo.Worker.Tests/Report/ClaudeHistoryReaderTests.cs index bc298e4..6f6015a 100644 --- a/tests/ClaudeDo.Worker.Tests/Report/ClaudeHistoryReaderTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Report/ClaudeHistoryReaderTests.cs @@ -27,6 +27,22 @@ public class ClaudeHistoryReaderTests : IDisposable private static string Json(string s) => System.Text.Json.JsonSerializer.Serialize(s); + [Fact] + public async Task SessionSpanningTwoRepos_KeepsLastSummaryPerRepo() + { + WriteSession("proj", "s.jsonl", + AssistantLine(@"C:\Dev\Repos\A", "2026-06-01T08:00:00Z", "summary A"), + AssistantLine(@"C:\Dev\Repos\B", "2026-06-01T09:00:00Z", "summary B")); + + var reader = new ClaudeHistoryReader(_root); + var result = await reader.ReadAsync(new DateOnly(2026, 6, 1), new DateOnly(2026, 6, 3), + Array.Empty()); + + Assert.Equal(2, result.Count); + Assert.Equal(new[] { "summary A" }, result.Single(r => r.RepoPath.EndsWith("A")).Days.Single().Summaries); + Assert.Equal(new[] { "summary B" }, result.Single(r => r.RepoPath.EndsWith("B")).Days.Single().Summaries); + } + [Fact] public async Task Extracts_Prompts_And_Last_Assistant_Summary_GroupedByRepoAndDay() {