@mrciphersmith/keryx 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli.js +125 -51
  2. package/package.json +1 -1
  3. package/src/gdskills/bundled/install-manifest.json +231 -2
  4. package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
  5. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
  6. package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
  7. package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
  8. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
  9. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
  10. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
  11. package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
  12. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
  13. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
  14. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
  15. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
  16. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
  17. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
  18. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
  19. package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
  20. package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
  21. package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
  22. package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
  23. package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
  24. package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
  25. package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
  26. package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
  27. package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
  28. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
  29. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
  30. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
  31. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
  32. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
  33. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
  34. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
  35. package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
  36. package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
  37. package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
  38. package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
  39. package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
  40. package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
  41. package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
  42. package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
  43. package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
  44. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
  45. package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
  46. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
  47. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
  48. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
  49. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
  50. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
  51. package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
  52. package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
  53. package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
  54. package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
  55. package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
  56. package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
  57. package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
  58. package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
  59. package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
  60. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
  61. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
  62. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
  63. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
  64. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
  65. package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
  66. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
  67. package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
@@ -0,0 +1,1881 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "reports": [
4
+ {
5
+ "schemaVersion": "1.0.0",
6
+ "skillId": "csharp-dotnet/dotnet-implementation",
7
+ "strictness": "high",
8
+ "trials": 10,
9
+ "triggerAccuracy": {
10
+ "truePositive": 2,
11
+ "falsePositive": 1,
12
+ "positives": 7,
13
+ "negatives": 8
14
+ },
15
+ "evidence": "authored",
16
+ "scenarios": [
17
+ {
18
+ "id": "trigger-positive-1",
19
+ "kind": "trigger-positive",
20
+ "prompt": "Build a new order-placement endpoint in this ASP.NET Core API",
21
+ "strictness": "high",
22
+ "trials": 1,
23
+ "passes": 1,
24
+ "passRate": 1,
25
+ "passAtK": 1,
26
+ "grader": "trigger-rank-fork-family",
27
+ "status": "ran",
28
+ "deterministic": true
29
+ },
30
+ {
31
+ "id": "trigger-positive-2",
32
+ "kind": "trigger-positive",
33
+ "prompt": "I need a background email sender that doesn't block the caller in C#",
34
+ "strictness": "high",
35
+ "trials": 1,
36
+ "passes": 0,
37
+ "passRate": 0,
38
+ "passAtK": 0,
39
+ "grader": "trigger-rank-fork-family",
40
+ "status": "ran",
41
+ "deterministic": true
42
+ },
43
+ {
44
+ "id": "trigger-positive-3",
45
+ "kind": "trigger-positive",
46
+ "prompt": "How should I wire this new repository into the DI container?",
47
+ "strictness": "high",
48
+ "trials": 1,
49
+ "passes": 0,
50
+ "passRate": 0,
51
+ "passAtK": 0,
52
+ "grader": "trigger-rank-fork-family",
53
+ "status": "ran",
54
+ "deterministic": true
55
+ },
56
+ {
57
+ "id": "trigger-positive-4",
58
+ "kind": "trigger-positive",
59
+ "prompt": "Should this order confirmation type be a record or a class?",
60
+ "strictness": "high",
61
+ "trials": 1,
62
+ "passes": 1,
63
+ "passRate": 1,
64
+ "passAtK": 1,
65
+ "grader": "trigger-rank-fork-family",
66
+ "status": "ran",
67
+ "deterministic": true
68
+ },
69
+ {
70
+ "id": "trigger-positive-5",
71
+ "kind": "trigger-positive",
72
+ "prompt": "Add cancellation support to this async database call",
73
+ "strictness": "high",
74
+ "trials": 1,
75
+ "passes": 0,
76
+ "passRate": 0,
77
+ "passAtK": 0,
78
+ "grader": "trigger-rank-fork-family",
79
+ "status": "ran",
80
+ "deterministic": true
81
+ },
82
+ {
83
+ "id": "trigger-positive-6",
84
+ "kind": "trigger-positive",
85
+ "prompt": "What's the right way to inject a database context into a singleton cache service?",
86
+ "strictness": "high",
87
+ "trials": 1,
88
+ "passes": 0,
89
+ "passRate": 0,
90
+ "passAtK": 0,
91
+ "grader": "trigger-rank-fork-family",
92
+ "status": "ran",
93
+ "deterministic": true
94
+ },
95
+ {
96
+ "id": "trigger-positive-7",
97
+ "kind": "trigger-positive",
98
+ "prompt": "Write an EF Core query for fetching recent orders with their line items",
99
+ "strictness": "high",
100
+ "trials": 1,
101
+ "passes": 0,
102
+ "passRate": 0,
103
+ "passAtK": 0,
104
+ "grader": "trigger-rank-fork-family",
105
+ "status": "ran",
106
+ "deterministic": true
107
+ },
108
+ {
109
+ "id": "trigger-negative-1",
110
+ "kind": "trigger-negative",
111
+ "prompt": "Implement this feature in Java using Spring Boot's @Service annotation",
112
+ "strictness": "high",
113
+ "trials": 1,
114
+ "passes": 0,
115
+ "passRate": 0,
116
+ "passAtK": 0,
117
+ "grader": "trigger-rank-fork-family",
118
+ "status": "ran",
119
+ "deterministic": true
120
+ },
121
+ {
122
+ "id": "trigger-negative-2",
123
+ "kind": "trigger-negative",
124
+ "prompt": "Add a Go worker pool under cmd/worker that bounds concurrency with errgroup",
125
+ "strictness": "high",
126
+ "trials": 1,
127
+ "passes": 1,
128
+ "passRate": 1,
129
+ "passAtK": 1,
130
+ "grader": "trigger-rank-fork-family",
131
+ "status": "ran",
132
+ "deterministic": true
133
+ },
134
+ {
135
+ "id": "trigger-negative-3",
136
+ "kind": "trigger-negative",
137
+ "prompt": "Implement this feature in a Python FastAPI service with Pydantic models",
138
+ "strictness": "high",
139
+ "trials": 1,
140
+ "passes": 1,
141
+ "passRate": 1,
142
+ "passAtK": 1,
143
+ "grader": "trigger-rank-fork-family",
144
+ "status": "ran",
145
+ "deterministic": true
146
+ },
147
+ {
148
+ "id": "trigger-negative-4",
149
+ "kind": "trigger-negative",
150
+ "prompt": "Implement this SwiftUI view with @State and a binding for the text field",
151
+ "strictness": "high",
152
+ "trials": 1,
153
+ "passes": 1,
154
+ "passRate": 1,
155
+ "passAtK": 1,
156
+ "grader": "trigger-rank-fork-family",
157
+ "status": "ran",
158
+ "deterministic": true
159
+ },
160
+ {
161
+ "id": "trigger-negative-5",
162
+ "kind": "trigger-negative",
163
+ "prompt": "Add this Jetpack Compose composable for the account settings screen",
164
+ "strictness": "high",
165
+ "trials": 1,
166
+ "passes": 1,
167
+ "passRate": 1,
168
+ "passAtK": 1,
169
+ "grader": "trigger-rank-fork-family",
170
+ "status": "ran",
171
+ "deterministic": true
172
+ },
173
+ {
174
+ "id": "trigger-negative-6",
175
+ "kind": "trigger-negative",
176
+ "prompt": "Implement this Flutter widget as a StatefulWidget with a form field",
177
+ "strictness": "high",
178
+ "trials": 1,
179
+ "passes": 1,
180
+ "passRate": 1,
181
+ "passAtK": 1,
182
+ "grader": "trigger-rank-fork-family",
183
+ "status": "ran",
184
+ "deterministic": true
185
+ },
186
+ {
187
+ "id": "trigger-negative-7",
188
+ "kind": "trigger-negative",
189
+ "prompt": "Review this C# diff for async void misuse and swallowed exceptions",
190
+ "strictness": "high",
191
+ "trials": 1,
192
+ "passes": 1,
193
+ "passRate": 1,
194
+ "passAtK": 1,
195
+ "grader": "trigger-rank-fork-family",
196
+ "status": "ran",
197
+ "deterministic": true
198
+ },
199
+ {
200
+ "id": "trigger-negative-8",
201
+ "kind": "trigger-negative",
202
+ "prompt": "dotnet build is failing with a NuGet version conflict, fix it",
203
+ "strictness": "high",
204
+ "trials": 1,
205
+ "passes": 1,
206
+ "passRate": 1,
207
+ "passAtK": 1,
208
+ "grader": "trigger-rank-fork-family",
209
+ "status": "ran",
210
+ "deterministic": true
211
+ },
212
+ {
213
+ "id": "async-void-avoidance",
214
+ "kind": "behavior",
215
+ "prompt": "I need to send an order-shipped email notification from OrderService without making the caller of PlaceOrder wait for the email to go out. What's the right way to write this method in C#?",
216
+ "strictness": "high",
217
+ "trials": 10,
218
+ "passes": 10,
219
+ "passRate": 1,
220
+ "passAtK": 1,
221
+ "grader": "judge",
222
+ "status": "ran",
223
+ "trialRecords": [
224
+ {
225
+ "output": "Short answer: don't do the fire-and-forget directly inside `OrderService.PlaceOrder` — and definitely not with `async void`. There are three reasonable shapes, in increasing order of how much they decouple the email from the request.\n\n## The three options\n\n**Option A — channel/queue + a background consumer (usually the right one).**\n`PlaceOrder` writes a message to an in-process `Channel<OrderShipped>` (or a real queue if you already have one) and returns. A `BackgroundService` reads from the channel and sends the email.\n\n```csharp\npublic sealed class OrderService(\n IOrderRepository repository,\n ChannelWriter<OrderShipped> notifications,\n TimeProvider timeProvider)\n{\n public async Task<Order> PlaceOrderAsync(PlaceOrderCommand command, CancellationToken ct)\n {\n ArgumentNullException.ThrowIfNull(command);\n\n var order = Order.Create(command, timeProvider.GetUtcNow());\n await repository.AddAsync(order, ct);\n await repository.SaveChangesAsync(ct);\n\n // Do not await the send. Do not swallow failure silently either --\n // the queued message is the handoff, and the consumer owns retries.\n await notifications.WriteAsync(new OrderShipped(order.Id, order.CustomerEmail), ct);\n\n return order;\n }\n}\n```\n\nThe consumer is where the actual `SendAsync` and its error handling live:\n\n```csharp\npublic sealed class OrderShippedEmailWorker(\n ChannelReader<OrderShipped> reader,\n IEmailSender emailSender,\n ILogger<OrderShippedEmailWorker> logger) : BackgroundService\n{\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var message in reader.ReadAllAsync(stoppingToken))\n {\n try\n {\n await emailSender.SendOrderShippedAsync(message, stoppingToken);\n }\n catch (Exception ex)\n {\n // Log and let the message be retried at the queue level;\n // never let one failure kill the worker loop.\n logger.LogError(ex, \"Failed to send order-shipped email for order {OrderId}\", message.OrderId);\n }\n }\n }\n}\n```\n\nRegister the channel as a singleton pair and the worker as a hosted service. `Channel.CreateBounded(...)` if you want backpressure so a burst of orders can't grow memory unbounded.\n\n**Option B — outbox table + a poller/worker.** If the email must not be lost, write an `OutboxMessage` row in the *same transaction* as the order, then have a worker drain the outbox. This is A's reliability story done properly: the \"enqueue\" and the order commit or roll back together. Reach for this the moment \"the email didn't send\" is a real business problem rather than a nice-to-have.\n\n**Option C — inject an `INotificationScheduler` and let `PlaceOrder` await the *schedule*, not the send.** Thin wrapper over A or B; useful if you want the seam to be mockable without `OrderService` knowing about channels.\n\n## What not to do\n\n```csharp\n// NO: async void. Exceptions here crash the process, and the caller\n// has no way to await it -- tests will flake and logs will be sad.\npublic async void PlaceOrder(PlaceOrderCommand command) { ... }\n\n// NO: Task.Run fire-and-forget. Under ASP.NET Core this races with\n// request teardown: the DI scope is disposed, the DbContext is gone,\n// and the send may run after the response has already been written.\n_ = Task.Run(() => emailSender.SendAsync(...));\n\n// NO: .Result / .Wait() anywhere near this.\n```\n\nThe two failure modes worth naming explicitly, because they're the reason the obvious-looking code is wrong:\n\n1. **`async void`** has no `Task` for anyone to observe. An exception thrown inside it is re-raised on the thread pool and terminates the process. It's only ever correct for a genuine event handler (`button.Click += async (s, e) => ...`), and even then you want a top-level try/catch.\n2. **`Task.Run` from a request handler** captures nothing about the request except the objects you closed over — including a `Scoped` `DbContext` that the request's scope will dispose the instant the response completes. The email send then fails with `ObjectDisposedException` (or, worse, succeeds intermittently), which is exactly the kind of bug that only shows up under load.\n\n## Lifetimes, since they bite here\n\nIf `OrderService` is `Scoped` and your notification channel is `Singleton`, that's fine — a channel is just a handle, not a `DbContext`. But if you're tempted to inject the `DbContext` or the `IEmailSender`-with-a-scoped-dependency straight into a `Singleton` worker, don't. The `BackgroundService` is a singleton by nature, so anything scoped it needs must be resolved per-message through `IServiceScopeFactory`:\n\n```csharp\nusing var scope = scopeFactory.CreateScope();\nvar sender = scope.ServiceProvider.GetRequiredService<IEmailSender>();\nawait sender.SendOrderShippedAsync(message, stoppingToken);\n```\n\n## Verification\n\nBefore you call this done:\n\n- `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all exit 0.\n- `PlaceOrderAsync` has no `async void`, no `.Result`/`.Wait()`, no bare `Task.Run`.\n- The new public method's parameters and return are explicitly nullable-annotated (and `PlaceOrderCommand` should be non-null — that's what the `ArgumentNullException.ThrowIfNull` is for).\n- A test exists that places an order and asserts the *message was enqueued* (option A/C) or the *outbox row was written in the same transaction* (option B) — that's the seam you can actually test; the send itself gets its own worker test with a fake `IEmailSender`.\n- The `CancellationToken` flows from `PlaceOrderAsync` into the repository calls, and the worker uses its own `stoppingToken` rather than the request's.\n\nPick A unless email loss is a real business problem, in which case pick B. Both are \"fire and forget\" done honestly — the send is decoupled from the caller, but nothing is silently dropped and nothing outlives its DI scope.",
226
+ "outputSha256": "ec13f24236f09ca4e85ee1fd2b60b4ff9ec5a8b8f6d6a3e1200ceae71e7a4ea1",
227
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
228
+ "deterministic": [],
229
+ "judge": {
230
+ "verdict": "pass",
231
+ "reason": "Criterion 1: answer says 'definitely not with async void' and keeps PlaceOrderAsync returning Task. Criterion 2: concrete mechanism — writes to Channel<OrderShipped> and a BackgroundService consumer reads/sends, with try/catch logging. Criterion 3: 'async void has no Task for anyone to observe. An exception thrown inside it is re-raised on the thread pool and terminates the process.' No fail criterion met."
232
+ },
233
+ "passed": true
234
+ },
235
+ {
236
+ "output": "Short answer: don't write `async void SendShippedEmail(...)` and call it inline. \"Don't make the caller wait\" has two very different meanings, and they need different implementations:\n\n**Fire-and-forget within the request (no durability, best-effort)** — only if losing an email on process restart/crash is acceptable:\n\n```csharp\npublic async Task PlaceOrderAsync(Order order, CancellationToken cancellationToken)\n{\n // ... persist the order, the actual work the caller needs completed ...\n _ = SendShippedEmailSafelyAsync(order, cancellationToken);\n}\n\nprivate async Task SendShippedEmailSafelyAsync(Order order, CancellationToken cancellationToken)\n{\n try\n {\n await _emailSender.SendShippedAsync(order, cancellationToken);\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Failed to send shipped email for order {OrderId}\", order.Id);\n }\n}\n```\n\nNote the shape: the background method still returns `Task` (never `async void`), it is assigned to a discard `_` so the compiler doesn't warn, and it catches its own exceptions — an unobserved faulted `Task` is invisible or, with `async void`, crashes the process. Pass the ambient `CancellationToken` only if you actually want the email cancelled when the request aborts; for send-after-response semantics you usually want a fresh token instead (see below).\n\n**Durable / production default (recommended)** — the caller doesn't wait, and the email survives a crash or a failed send. Enqueue a message and let a background worker deliver it:\n\n```csharp\npublic async Task PlaceOrderAsync(Order order, CancellationToken cancellationToken)\n{\n await _orderRepository.AddAsync(order, cancellationToken);\n\n // Outbox row written in the same transaction as the order.\n await _outbox.EnqueueAsync(new SendShippedEmail(order.Id), cancellationToken);\n}\n```\n\nA hosted `BackgroundService` then drains the outbox, retries with backoff, and moves permanently-failed items to a dead-letter table. This is the only version that gives you \"the order is placed\" and \"the email eventually goes out\" as independent, individually-durable facts — with in-request fire-and-forget, a process recycle between enqueue and send silently drops the notification, and no one is retrying it.\n\n**Corrections to a couple of tempting shortcuts:**\n\n- `Task.Run(() => SendShippedEmailAsync(order))` inside an ASP.NET Core request does *not* give you anything the first approach doesn't — it still loses the work on shutdown, it steals a thread pool thread from an async I/O operation, and it still swallows faults unless you handle them. Don't reach for it just because \"the request thread shouldn't wait.\"\n- Do **not** capture the request's `DbContext` or a `Scoped` email-sender that depends on one into a background task that outlives the request. The scope is disposed when the response completes; resolve a fresh scope via `IServiceScopeFactory` (or let the outbox worker own its own scope per message) for anything that runs past the request.\n\nIf you tell me which of the two semantics you actually need — best-effort fire-and-forget versus guaranteed delivery — and whether the project already has an outbox or a message broker, I'll give you the exact method and DI registrations for it rather than both shapes.",
237
+ "outputSha256": "8120a6ae094f0e945bd0f827f973458a6f227a9b9c64417e7e718e6d568debe1",
238
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
239
+ "deterministic": [],
240
+ "judge": {
241
+ "verdict": "pass",
242
+ "reason": "Answer states method must remain Task-returning, never async void ('the background method still returns Task (never async void)'). Concrete mechanisms: discard `_ = SendShippedEmailSafelyAsync(...)` with try/catch logging, and outbox + BackgroundService. Risk stated: 'with async void, crashes the process' and unobserved faulted Task invisible. No async void recommendation."
243
+ },
244
+ "passed": true
245
+ },
246
+ {
247
+ "output": "Short answer: **don't make it `async void`.** `async void` is only for genuine event handlers. `PlaceOrder` should stay `async Task`, and the email should be dispatched in a way that doesn't block it — either by enqueuing the work and returning, or by awaiting a fire-and-forget *task* that you deliberately don't await, with the exception handling made explicit.\n\n## The core decision\n\nThere are three legitimate shapes, and one common wrong shape:\n\n| Approach | Caller waits? | Exceptions observable? | Use when |\n|---|---|---|---|\n| `await _email.SendAsync(...)` | Yes | Yes | You actually want ordering/durability guarantees |\n| Enqueue to a background queue / `Channel` / outbox | No | Yes (in the worker) | **Recommended** — email is decoupled work |\n| `_ = SendEmailAndSwallowAsync(...)` | No | Only if you log inside | Quick hack, in-process only |\n| `async void SendEmailAsync(...)` | No | **No — crashes the process** | Never (except real event handlers) |\n\n## Recommended: enqueue, don't fire-and-forget\n\nMake the enqueue the only thing `PlaceOrder` touches. The email sending happens on a hosted background worker:\n\n```csharp\npublic interface IEmailQueue\n{\n ValueTask EnqueueAsync(OrderShippedEmail email, CancellationToken ct = default);\n}\n\npublic sealed class OrderService\n{\n private readonly IOrderRepository _repository;\n private readonly IEmailQueue _emailQueue;\n private readonly ILogger<OrderService> _logger;\n\n public OrderService(\n IOrderRepository repository,\n IEmailQueue emailQueue,\n ILogger<OrderService> logger)\n {\n _repository = repository;\n _emailQueue = emailQueue;\n _logger = logger;\n }\n\n public async Task<Order> PlaceOrderAsync(\n PlaceOrderCommand command,\n CancellationToken cancellationToken = default)\n {\n ArgumentNullException.ThrowIfNull(command);\n\n var order = Order.Create(command);\n await _repository.AddAsync(order, cancellationToken);\n\n // Enqueue is fast, in-process, and durable-ish; the caller\n // only waits for the enqueue, not for SMTP to round-trip.\n await _emailQueue.EnqueueAsync(\n new OrderShippedEmail(order.Id, order.CustomerEmail),\n cancellationToken);\n\n return order;\n }\n}\n```\n\nBack it with a `System.Threading.Channels.Channel<T>` (bounded, so a flood of orders applies backpressure instead of eating memory) and drain it from an `IHostedService`/`BackgroundService`. That worker owns the `await SendAsync(...)`, the retries, and the logging — so exceptions *are* observable, which the fire-and-forget version loses.\n\n## If you insist on in-process fire-and-forget\n\nThen make the exception handling explicit instead of silently dropping it. A `Task`-returning helper that never throws, launched with the discard operator:\n\n```csharp\n_ = SendEmailSafelyAsync(order, cancellationToken);\n\nprivate async Task SendEmailSafelyAsync(Order order, CancellationToken ct)\n{\n try\n {\n await _emailSender.SendAsync(order, ct);\n }\n catch (Exception ex)\n {\n // Fire-and-forget has no caller to observe this; log it or\n // the failure vanishes entirely.\n _logger.LogError(ex, \"Failed to send shipped email for order {OrderId}\", order.Id);\n }\n}\n```\n\nThe `_ =` discard is load-bearing: it tells the compiler and the next reader \"this task is intentionally unawaited.\" Without it, the analyzer flags an unawaited task; with `async void`, there's nothing to flag because the failure is already unrecoverable.\n\n## Rules that apply here\n\n- **`PlaceOrderAsync` stays `async Task`.** Never `async void` — an unhandled exception there can't be caught by the caller and takes down the process.\n- **Don't block on the email with `.Result` / `.Wait()` / `.GetAwaiter().GetResult()`.** If you want the caller to wait for the email, `await` it; if you don't, don't block on it either. Sync-over-async can deadlock the moment the continuation wants the context the blocking thread holds.\n- **Don't inject a `Scoped` `DbContext` (or anything holding one) into a `Singleton` background worker.** That captures the first request's context for the app's whole lifetime. Resolve per-message through `IServiceScopeFactory.CreateScope()` inside the worker.\n- **Thread the `CancellationToken` through.** `PlaceOrderAsync`'s token cancels *the caller's* wait, not necessarily the email send — the queue worker should own the lifetime of the email work. Be deliberate about which token means what; don't pass the request token into work that's meant to outlive the request.\n- **Be honest about durability.** A channel in one process is lost on restart or crash. If the email must not be lost, that's an outbox table written in the same transaction as the order, drained by a background job — not a `Channel<T>`.\n\n## Verification\n\nBefore calling it done:\n\n- `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all exit 0.\n- `PlaceOrderAsync` is `Task`-returning (or `Task<Order>`); nothing in the path is `async void`.\n- No `.Result`/`.Wait()` anywhere in the new code.\n- The email worker (if you added one) resolves its `DbContext`/scoped services via `IServiceScopeFactory`, and any `IDisposable`/`IAsyncDisposable` it creates is wrapped in `using`/`await using`.\n- The failure path is observable somewhere — logs at minimum, ideally a retry/outbox.",
248
+ "outputSha256": "504b9a63b1c610459451fe3f81c56e063b0a74468d1f1120fa56ce63de1ccaff",
249
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
250
+ "deterministic": [],
251
+ "judge": {
252
+ "verdict": "pass",
253
+ "reason": "Answer states PlaceOrderAsync/notification stays async Task, never async void (\"PlaceOrderAsync stays async Task. Never async void\"). Concrete non-waiting mechanisms: enqueue to Channel/IHostedService/BackgroundService, or `_ = SendEmailSafelyAsync(...)` with try/catch logging. Risk stated: async void exception can't be caught by caller and crashes the process. No fail criterion met."
254
+ },
255
+ "passed": true
256
+ },
257
+ {
258
+ "output": "The key requirement — the caller of `PlaceOrder` must not wait for the email — rules out simply making `PlaceOrder` async and awaiting the send. You need a deliberate \"fire and forget with accountability\" design, not `async void`.\n\n## The wrong answer to name explicitly\n\n```csharp\n// DO NOT DO THIS\npublic void PlaceOrder(Order order)\n{\n _repo.Save(order);\n _mailer.SendOrderShippedAsync(order); // unawaited Task — exceptions vanish or crash\n}\n```\n\nAn unobserved `Task` (or an `async void` helper) means: if the mail send throws, nobody catches it, it surfaces as an unobserved task exception (or crashes the process for `async void`), and you lose any record that the send failed. \"Doesn't block the caller\" must not degrade into \"nobody is responsible for the outcome.\"\n\n## The right shape: enqueue, then process off the request path\n\nDescribe the work as a durable unit and hand it to something that owns its lifecycle.\n\n```csharp\npublic interface IOrderShippedNotifier\n{\n ValueTask EnqueueAsync(Order order, CancellationToken ct = default);\n}\n\npublic sealed class OrderService(IOrderRepository repo, IOrderShippedNotifier notifier)\n{\n public async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct = default)\n {\n ArgumentNullException.ThrowIfNull(request);\n\n var order = Order.From(request);\n await repo.SaveAsync(order, ct);\n\n // Enqueue is cheap; it does NOT send the email inline.\n await notifier.EnqueueAsync(order, ct);\n\n return order;\n }\n}\n```\n\n`EnqueueAsync` returns `ValueTask` because it is usually a quick queue/hosted-channel write with no real allocation. `PlaceOrderAsync` awaits the enqueue (so the job is durably recorded before the caller returns) but never awaits the actual SMTP/API call.\n\nThe email send then lives in a background consumer — an `IHostedService` reading from a `Channel<OrderShippedJob>` — which is where the real `SendOrderShippedAsync` runs, with its own try/catch and retry/dead-letter handling. In a real service, back the queue with the outbox pattern (a row written in the same transaction as the order) rather than an in-memory `Channel`, so a process restart doesn't silently drop the notification.\n\n## Why not the other tempting shortcuts\n\n- **Naked `Task.Run(() => _mailer.SendAsync(order))`** — no cancellation, no error handling, no shutdown coordination; the task can be killed mid-flight when the app stops.\n- **`async void` event-style helper** — only acceptable for a genuine event handler; everywhere else an exception it throws cannot be caught by any caller.\n- **`_mailer.SendAsync(order).ConfigureAwait(false)` without awaiting** — still an unobserved task; `ConfigureAwait(false)` does not make it safe, it only drops the synchronization context for the continuation.\n- **`.Result` / `.Wait()` inside a wrapper** — sync-over-async; can deadlock and defeats the \"don't make the caller wait\" goal in the worst way.\n\n## Lifetime and cleanup notes that this design forces you to get right\n\n- The notifier (or the hosted consumer) should be a **Singleton** — it holds the queue, not request state. It must **not** capture the Scoped `DbContext` directly; resolve the scope per job via `IServiceScopeFactory`.\n- `PlaceOrderAsync` threads `CancellationToken` as the last parameter so a cancelled request aborts before enqueue.\n- If the queue is backed by anything `IDisposable`/`IAsyncDisposable` (a channel-completing consumer, a client), the owning type implements disposal and drains gracefully on shutdown rather than dropping in-flight notifications.\n\n## Verification checklist specific to this feature\n\n- `PlaceOrderAsync` awaits only `SaveAsync` and `EnqueueAsync`; the network send is never awaited on the request path.\n- The send path has explicit try/catch and a retry or dead-letter outcome — a failed notification is recorded, not swallowed.\n- Nothing is `async void`; the background consumer is a proper `Task`-returning loop in an `IHostedService`.\n- The notifier is Singleton and does not hold a captured `DbContext`; scopes are created per job.\n- `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes` all pass, and there's a test asserting `PlaceOrderAsync` returns without the mailer's `SendAsync` having been awaited.",
259
+ "outputSha256": "ee72c7f0495786ada1af818b70c5a06380fe9cf55a57ef8ccffd6922cbc8ff59",
260
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
261
+ "deterministic": [],
262
+ "judge": {
263
+ "verdict": "pass",
264
+ "reason": "Answer keeps notifier Task-returning (EnqueueAsync returns ValueTask, background consumer is a Task-returning IHostedService loop), gives concrete non-async-void mechanism (enqueue to Channel/IHostedService, outbox, caller awaits only enqueue not the send), and states async void risk: 'an exception it throws cannot be caught by any caller' and crashes process. No fail criterion met."
265
+ },
266
+ "passed": true
267
+ },
268
+ {
269
+ "output": "# Async fire-and-forget email from `OrderService`\n\nThe short answer: don't make the email path `async void`, and don't call `.Result`/`.Wait()`. Instead, depend on an abstraction that owns the background delivery, hand it the work, and return. There are three common shapes; pick based on what you already have in the service.\n\n## Option A: An enqueuing notifier (recommended for a service like this)\n\nMake the email send an enqueue operation, not the send itself. `PlaceOrder` awaits only the enqueue; a background worker drains the queue.\n\n```csharp\npublic interface IEmailNotifier\n{\n ValueTask EnqueueOrderShippedAsync(OrderShippedEmail email, CancellationToken ct = default);\n}\n\npublic sealed class OrderService\n{\n private readonly IOrderRepository _orders;\n private readonly IEmailNotifier _notifier;\n private readonly ILogger<OrderService> _logger;\n\n public OrderService(\n IOrderRepository orders,\n IEmailNotifier notifier,\n ILogger<OrderService> logger)\n {\n _orders = orders;\n _notifier = notifier;\n _logger = logger;\n }\n\n public async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct = default)\n {\n ArgumentNullException.ThrowIfNull(request);\n\n var order = await _orders.CreateAsync(request, ct);\n\n // Fast: just enqueue. The caller does not wait on SMTP.\n await _notifier.EnqueueOrderShippedAsync(\n new OrderShippedEmail(order.Id, order.CustomerEmail),\n ct);\n\n return order;\n }\n}\n```\n\nThe `IEmailNotifier` implementation backs onto a durable queue (a Channel, a message bus, or a DB outbox table), and a `BackgroundService`/worker consumes it and does the actual `SmtpClient`/API call. This is the version I'd push for in a real service — the reason is durability. If the process recycles the instant after `PlaceOrder` returns, an in-memory fire-and-forget send is simply lost; an enqueue plus a worker survives that. It also gives you retries and a dead-letter path for free.\n\n## Option B: `IHostedService` + `Channel<T>` if there's no existing bus\n\nIf you don't have a message bus, a bounded `System.Threading.Channels.Channel<OrderShippedEmail>` behind `IEmailNotifier`, with the `BackgroundService` reading it, is the standard in-process pattern. Same call site as Option A — the DI wiring is where the difference lives:\n\n```csharp\nbuilder.Services.AddSingleton(Channel.CreateBounded<OrderShippedEmail>(\n new BoundedChannelOptions(capacity: 1000)\n {\n FullMode = BoundedChannelFullMode.Wait,\n }));\n\nbuilder.Services.AddSingleton<IEmailNotifier, ChannelEmailNotifier>();\nbuilder.Services.AddHostedService<EmailDispatchWorker>();\n```\n\nRegister the notifier as **Singleton** (it just holds the channel), and if the worker touches EF Core, resolve the `DbContext` per-message via `IServiceScopeFactory` inside the worker — never capture a Scoped `DbContext` into the Singleton.\n\n## Option C: `IServiceScopeFactory` + a scoped send, when there's no queue\n\nIf you truly cannot add a background worker yet and want a bounded, best-effort send, spawn a scope and let it run detached — but do this consciously, because it is *not* durable:\n\n```csharp\npublic sealed class OrderService\n{\n private readonly IServiceScopeFactory _scopes;\n private readonly ILogger<OrderService> _logger;\n\n public OrderService(IServiceScopeFactory scopes, ILogger<OrderService> logger)\n {\n _scopes = scopes;\n _logger = logger;\n }\n\n public async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct = default)\n {\n ArgumentNullException.ThrowIfNull(request);\n var order = await _orders.CreateAsync(request, ct);\n\n _ = SendShippedEmailInBackgroundAsync(order);\n\n return order;\n }\n\n private async Task SendShippedEmailInBackgroundAsync(Order order)\n {\n // New scope: the request scope (and its DbContext) will be disposed\n // by the time this runs, so we cannot reuse it.\n using var scope = _scopes.CreateScope();\n var sender = scope.ServiceProvider.GetRequiredService<IEmailSender>();\n try\n {\n await sender.SendOrderShippedAsync(order);\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Order-shipped email failed for order {OrderId}\", order.Id);\n }\n }\n}\n```\n\n## What not to do\n\n- **`async void SendShippedEmail(...)`** — an exception thrown inside it cannot be awaited or caught by any caller and will tear down the process. This is the single most common way this task goes wrong.\n- **`_ = sender.SendAsync(order)`** where `sender` is the request-scoped service — the DI scope may be disposed underneath it, and `SendAsync` throws `ObjectDisposedException` on the `DbContext` inside. If you detach the work, give it its own scope (Option C) or its own queue (A/B).\n- **`Task.Run(() => sender.SendAsync(order))`** without a scope — same disposal bug as above, plus you've just moved the work to a thread-pool thread with no ownership of the result.\n- **`sender.SendAsync(order).Wait()` / `.Result`** — sync-over-async; can deadlock and defeats the whole point.\n- **`CancellationToken` from the request carried into the detached send** — if the caller's request is cancelled, your background send gets cancelled too. Pass `CancellationToken.None` (or a token owned by the worker's lifetime) into the detached work.\n\n## Choosing\n\n- Have a message bus or an outbox table? **Option A.** Durable, retryable, survives restart.\n- No bus, but the app is a long-running host with `AddHostedService` available? **Option B.** In-process queue + worker; still lost on ungraceful shutdown mid-queue, but far better than a bare detached `Task`.\n- Prototype, or a genuinely disposable best-effort send? **Option C**, scoped, with a try/catch and a logged error. Accept that a crash after `PlaceOrder` returns may drop the email.\n\nIn all three, the call site `await`s only the enqueue/spawn, so `PlaceOrder`'s caller never waits on SMTP.\n\nIf you tell me which of these matches your existing infrastructure (message bus? `IHostedService`? just a request-scoped `IEmailSender`?), I can narrow this to the one you should actually ship and wire the DI registration for it.",
270
+ "outputSha256": "22447767d20f718094458e41b2c5ecb213c94a0d0efc3b781eab617abd8a949a",
271
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
272
+ "deterministic": [],
273
+ "judge": {
274
+ "verdict": "pass",
275
+ "reason": "Answer states method should stay Task-returning (Option C: 'private async Task SendShippedEmailInBackgroundAsync'), gives concrete non-awaiting mechanisms (enqueue+BackgroundService/Channel, IHostedService, IServiceScopeFactory detached task with try/catch logging), and states async void risk: 'an exception thrown inside it cannot be awaited or caught by any caller and will tear down the process.'"
276
+ },
277
+ "passed": true
278
+ },
279
+ {
280
+ "output": "The key decision here is *which* non-blocking pattern you want, because \"don't make the caller wait\" has two very different shapes in C#: fire-and-forget (truly detached, caller never observes the result) and asynchronous-but-awaited (caller still awaits, but the thread isn't blocked). For an email side-effect that must actually happen, you almost never want true fire-and-forget — if `PlaceOrder` returns before the send is enqueued, a process crash or a scoped-lifetime disposal can silently drop the notification. The robust shape is: do the work that matters durably inside the request, and let a background worker drain it.\n\n**Option A — background queue (recommended for a real service).**\n\n```csharp\npublic interface IEmailQueue\n{\n ValueTask EnqueueAsync(OrderShippedEmail email, CancellationToken ct = default);\n}\n\npublic sealed class OrderService(IOrderRepository repository, IEmailQueue queue)\n{\n public async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct = default)\n {\n ArgumentNullException.ThrowIfNull(request);\n\n var order = await repository.CreateAsync(request, ct);\n\n // Enqueue only, do not send inline. This is fast (channel write),\n // and the actual SMTP/API call happens in a BackgroundService that\n // owns its own scope and retries on failure.\n await queue.EnqueueAsync(new OrderShippedEmail(order.Id, order.CustomerEmail), ct);\n\n return order;\n }\n}\n```\n\n`PlaceOrderAsync` returns as soon as the message is durably queued; the caller never waits on the SMTP/HTTP send. A `BackgroundService` consumes the channel with `await foreach (...)`, resolving a fresh scope (and thus a fresh `DbContext`/`IEmailSender`) per message. This is the only shape that survives a crash mid-send and gives you retries.\n\n**Option B — genuinely detached, best-effort fire-and-forget (only if a dropped email is acceptable).** If you truly must not enqueue and don't care about the result, the method must *not* be `async void`. Instead:\n\n```csharp\npublic Order PlaceOrder(OrderRequest request)\n{\n ArgumentNullException.ThrowIfNull(request);\n var order = repository.Create(request);\n\n // Deliberately detached. Note the two guards:\n // - the exceptions are observed here (async void would crash the process)\n // - the scope is created here, because the request scope may be disposed\n // by the time the continuation runs.\n var scopeFactory = /* injected IServiceScopeFactory */ _scopeFactory;\n var orderId = order.Id;\n var email = order.CustomerEmail;\n\n _ = Task.Run(async () =>\n {\n try\n {\n using var scope = scopeFactory.CreateScope();\n var sender = scope.ServiceProvider.GetRequiredService<IEmailSender>();\n await sender.SendOrderShippedAsync(orderId, email, CancellationToken.None);\n }\n catch (Exception ex)\n {\n logger.LogError(ex, \"Order-shipped email failed for {OrderId}\", orderId);\n }\n });\n\n return order;\n}\n```\n\nNote what this costs you: no retry, no backpressure, no visibility, and it can be lost on shutdown because nothing tracks the in-flight task. I'd only reach for it for cosmetic notifications.\n\n**Things that are wrong here regardless of option:**\n\n- `async void SendEmail()` — you can't await it, can't catch it, and an exception it throws tears down the process. This is the trap people fall into when they want \"don't make the caller wait.\"\n- Calling `SendEmailAsync().Result` / `.Wait()` from inside `PlaceOrder` — that's sync-over-async; it blocks *and* risks deadlock. Defeats the whole point.\n- Injecting a `Scoped` `IEmailSender`/`DbContext` directly into a `Singleton` queue or the `Task.Run` capture — the scope may be gone when the send runs. Resolve it per-use from `IServiceScopeFactory`.\n- Capturing `order` (the entity) in the detached task instead of the plain values (`order.Id`, `order.CustomerEmail`) — you can end up touching a disposed `DbContext`.\n\n**My recommendation:** Option A. Make `PlaceOrderAsync` enqueue and return; let a hosted background service own the send and its scope. If the notification is genuinely optional and you refuse any queue infrastructure, Option B with the try/catch and scope factory, documented as best-effort.\n\nVerification for whichever you pick: `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all green; the new method is `Task`/`ValueTask`-returning (never `async void`); the queue or scope factory is resolved per-use rather than captured; and the enqueued/consumer path is covered by a test that asserts the email is sent without `PlaceOrderAsync` awaiting the send.",
281
+ "outputSha256": "b533ccc26dcccadbc7b07a7f05a53639d170823a14e31d4a8da608d12252949a",
282
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
283
+ "deterministic": [],
284
+ "judge": {
285
+ "verdict": "pass",
286
+ "reason": "Answer states method must not be async void (Option B: 'the method must *not* be `async void`'; warns async void 'tears down the process'). Concrete non-waiting mechanisms: Option A enqueue to IEmailQueue drained by BackgroundService; Option B Task.Run with try/catch logging via logger.LogError. Risk stated: async void 'can't await it, can't catch it, and an exception it throws tears down the process.'"
287
+ },
288
+ "passed": true
289
+ },
290
+ {
291
+ "output": "Short answer: don't make `PlaceOrder` block on SMTP. The right shape depends on what infrastructure you already have, so let me give you the three real options in order of preference, plus the anti-pattern to avoid.\n\n## The anti-pattern first\n\nDo **not** do this:\n\n```csharp\npublic async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct)\n{\n var order = await SaveOrderAsync(request, ct);\n _ = SendShippedNotificationAsync(order); // fire-and-forget -- do not do this\n return order;\n}\n```\n\nThat `_ =` looks harmless but it is a latent crash generator. The `Task` is never awaited, so:\n\n- Any exception inside `SendShippedNotificationAsync` is unobserved. In modern .NET an unobserved task exception is raised on the finalizer thread and can tear down the process.\n- The method can outlive its `Scoped` dependencies. If it captures the request's `DbContext` or a scoped repository, you are now touching a disposed object on a thread nobody owns.\n- In a web app, the request's `IHttpContextAccessor`/`HttpContext` is already being returned to the pool while the email is still going out. You can end up reading another tenant's context.\n- Under load, these orphaned tasks quietly pile up. There's no backpressure, no retry, no visibility.\n\n`async void` is the same story with a worse failure mode, and is even easier to write by accident:\n\n```csharp\nprivate async void SendShippedNotificationAsync(Order order) { ... } // never do this\n```\n\nIt is reserved for genuine UI event handlers, nowhere else. An exception thrown here has no `Task` to carry it and crashes the process on an `SynchronizationContext`-less thread.\n\n## Option 1 (best): an outbox + background worker\n\nIf email reliability actually matters — and \"order shipped\" usually does — write the notification intent to durable storage inside the same transaction that saves the order, then let a background service pick it up:\n\n```csharp\npublic async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct)\n{\n ArgumentNullException.ThrowIfNull(request);\n\n var order = await _repository.CreateAsync(\n Order.From(request), ct);\n\n // same DbContext, same transaction as the order insert\n _outbox.Add(new OutboxMessage(\n type: \"order.shipped\",\n payload: JsonSerializer.Serialize(new OrderShipped(order.Id, order.CustomerEmail))));\n\n await _unitOfWork.SaveChangesAsync(ct);\n return order;\n}\n```\n\nA `BackgroundService` on the same `IServiceScopeFactory` polls the outbox, sends via `IEmailSender`, and marks rows delivered. Because the intent is committed atomically with the order, a crash right after `PlaceOrder` returns still results in exactly one email eventually going out.\n\nThis is what you want unless you have a strong reason not to. It gives you retries, delivery visibility, and a transactionally-consistent guarantee. The pattern is a hair more setup (one table, one worker class) than what people reach for first, which is usually the reason it gets skipped — but \"sent an order confirmation email\" is exactly the kind of thing that must not be silently droppable.\n\n## Option 2: an in-process queue, if loss is tolerable\n\nIf the shop can tolerate \"occasionally no email on a hard crash,\" decouple through a channel instead of a raw task:\n\n```csharp\npublic sealed class OrderService\n{\n private readonly IOrderRepository _repository;\n private readonly IEmailQueue _queue;\n\n public OrderService(IOrderRepository repository, IEmailQueue queue)\n {\n _repository = repository;\n _queue = queue;\n }\n\n public async Task<Order> PlaceOrderAsync(OrderRequest request, CancellationToken ct)\n {\n ArgumentNullException.ThrowIfNull(request);\n\n var order = await _repository.CreateAsync(Order.From(request), ct);\n await _queue.EnqueueAsync(new OrderShipped(order.Id, order.CustomerEmail), ct);\n return order;\n }\n}\n```\n\n`IEmailQueue.EnqueueAsync` writes to a bounded `System.Threading.Channels.Channel<OrderShipped>`. A singleton `BackgroundService` owns the consumer:\n\n```csharp\npublic sealed class EmailQueue : IEmailQueue\n{\n private readonly Channel<OrderShipped> _channel =\n Channel.CreateBounded<OrderShipped>(new BoundedChannelOptions(1000)\n {\n FullMode = BoundedChannelFullMode.Wait,\n });\n\n public ValueTask EnqueueAsync(OrderShipped message, CancellationToken ct)\n => _channel.Writer.WriteAsync(message, ct);\n\n public IAsyncEnumerable<OrderShipped> ReadAllAsync(CancellationToken ct)\n => _channel.Reader.ReadAllAsync(ct);\n}\n```\n\nRegister `EmailQueue` as a singleton, and have the worker resolve `IEmailSender` **from a fresh scope per message**, never captured from `PlaceOrder`'s scope:\n\n```csharp\npublic sealed class EmailDispatchService : BackgroundService\n{\n private readonly IServiceScopeFactory _scopeFactory;\n private readonly EmailQueue _queue;\n\n public EmailDispatchService(IServiceScopeFactory scopeFactory, EmailQueue queue)\n {\n _scopeFactory = scopeFactory;\n _queue = queue;\n }\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var message in _queue.ReadAllAsync(stoppingToken))\n {\n await using var scope = _scopeFactory.CreateAsyncScope();\n var sender = scope.ServiceProvider.GetRequiredService<IEmailSender>();\n\n try\n {\n await sender.SendOrderShippedAsync(message, stoppingToken);\n }\n catch (Exception ex) when (ex is not OperationCanceledException)\n {\n // log; do not let one bad message kill the worker\n _logger.LogError(ex, \"Failed to send order-shipped for order {OrderId}\", message.OrderId);\n }\n }\n }\n}\n```\n\nTwo things worth calling out here. The queue being **bounded** matters — an unbounded channel is just a slower memory leak under a traffic spike, and when the mail provider is down you want `PlaceOrder` to eventually backpressure rather than OOM the process. And the `IServiceScopeFactory` is non-negotiable: a singleton worker that injects a scoped `IEmailSender` (or worse, a `DbContext`) directly is the single most common DI bug in this pattern. The container will either throw at startup or, if you're using a scope validator that's off, silently capture the first request's instance for the app's lifetime.\n\nNote that with this option the enqueue is in-memory, so a process crash between `SaveChangesAsync` and the worker draining the channel loses that email. That's the trade-off against Option 1.\n\n## Option 3: a real message broker\n\nIf you already run RabbitMQ, Azure Service Bus, SQS, or similar, publish to a topic instead of an in-process channel and let a separate consumer send the mail. This is Option 1's guarantees with more operational surface. Don't introduce a broker solely for order emails; if you already have one, use it.\n\n## Which one\n\n- \"Emails must reliably go out\" → **Option 1**, outbox + worker.\n- \"We can lose one occasionally, but keep it off the request thread\" → **Option 2**, bounded channel.\n- Already have a broker → **Option 3**.\n\n## Verification before you call it done\n\n- `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all green.\n- Every new async method returns `Task`/`Task<T>`/`ValueTask<T>`; nothing new is `async void`.\n- No `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` anywhere in the new path — `PlaceOrderAsync` stays genuinely async end-to-end.\n- Every scoped dependency the worker touches (`IEmailSender`, any `DbContext`) is resolved from a per-message scope via `IServiceScopeFactory`, not captured in the singleton's constructor.\n- The channel or outbox is bounded / has a retention policy, and a failed send is logged rather than swallowed or allowed to kill the worker.\n- New public parameters and return types carry explicit nullability annotations (`ArgumentNullException.ThrowIfNull` at the entry point, `?` where `null` is genuinely valid).\n\nThe one-line takeaway: the thing to avoid is `_ = SomeAsync()` and `async void`; the thing to reach for is a durable or bounded queue drained by a background worker that resolves its own scopes.",
292
+ "outputSha256": "ad300118c84190219d0c989805e620f685ff13194700cc9ab7ce00a044109c47",
293
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
294
+ "deterministic": [],
295
+ "judge": {
296
+ "verdict": "pass",
297
+ "reason": "Criterion 1: answer says notification method must stay Task-returning, warns 'async void ... reserved for genuine UI event handlers, nowhere else' and verification 'nothing new is async void'. Criterion 2: concrete mechanisms shown — outbox + BackgroundService, bounded Channel + EmailDispatchService, broker. Criterion 3: states async void exception 'has no Task to carry it and crashes the process'. No fail criterion: async void only warned against."
298
+ },
299
+ "passed": true
300
+ },
301
+ {
302
+ "output": "Don't reach for `async void` for this — that's the first instinct and it's the wrong tool. An `async void` method can't be awaited, and any exception it throws (SMTP timeout, DNS failure, whatever) escapes to the thread pool and crashes the process instead of reaching your caller or your logger. There are two clean shapes depending on whether the project already has a background job system.\n\n**Option A — `Task`-returning method the caller deliberately doesn't await (simplest, fine for small services)**\n\n```csharp\npublic async Task SendOrderShippedEmailAsync(int orderId, CancellationToken cancellationToken)\n{\n var order = await _orderRepository.GetByIdAsync(orderId, cancellationToken);\n if (order is null)\n {\n _logger.LogWarning(\"Order {OrderId} not found; skipping shipped email.\", orderId);\n return;\n }\n\n await _emailSender.SendAsync(order.CustomerEmail, orderId, cancellationToken);\n}\n```\n\nCall it from `PlaceOrder` like this:\n\n```csharp\npublic int PlaceOrder(PlaceOrderCommand command)\n{\n // ... place the order, persist it ...\n _ = _notificationQueue.EnqueueAsync(order.Id, CancellationToken.None);\n return order.Id;\n}\n```\n\nA few things that make this safe rather than a landmine:\n\n- The method is `Task`-returning, never `async void`, so any exception it throws is capturable.\n- It isn't left fully fire-and-forget. Enqueue it somewhere that owns retry and error logging — a `Channel<T>`-backed `BackgroundService`, a Hangfire/Quartz job, or an `IHostedService` worker. Discarding the task with `_ =` and letting a fault float away silently is the thing to avoid.\n- Comment why the discard is intentional. A stray `_ =` with no explanation reads like a bug six months later.\n- Fresh `CancellationToken.None`, or a token tied to the background worker's lifetime — not the HTTP request's `HttpContext.RequestAborted`, which is already cancelled the instant `PlaceOrder` returns.\n\n**Option B — hand it to the queue and let a worker do the I/O (what I'd pick if the project has any background infra)**\n\n`PlaceOrder` becomes fully synchronous; it just enqueues a message. The worker resolves a `IServiceScopeFactory`, creates a scope per message, resolves `IEmailSender`/`DbContext` from that scope, and sends. This avoids the scoped-service trap entirely — you can't inject a Scoped `DbContext` into a Singleton worker's field, you resolve it per-message.\n\n```csharp\npublic sealed class OrderShippedEmailWorker : BackgroundService\n{\n private readonly IServiceScopeFactory _scopeFactory;\n private readonly ILogger<OrderShippedEmailWorker> _logger;\n\n public OrderShippedEmailWorker(IServiceScopeFactory scopeFactory, ILogger<OrderShippedEmailWorker> logger)\n {\n _scopeFactory = scopeFactory;\n _logger = logger;\n }\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var orderId in _queue.ReadAllAsync(stoppingToken))\n {\n await using var scope = _scopeFactory.CreateAsyncScope();\n var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();\n try\n {\n await emailSender.SendOrderShippedAsync(orderId, stoppingToken);\n }\n catch (Exception ex)\n {\n _logger.LogError(ex, \"Shipped email failed for order {OrderId}.\", orderId);\n }\n }\n }\n}\n```\n\nWhat I would not do: `.Result` / `.Wait()` / `.GetAwaiter().GetResult()` anywhere in this path. Sync-over-async on the send turns a non-blocking design into a potential deadlock the moment the awaited call needs its continuation thread back.\n\nThen verify: `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all clean, and confirm you never introduced an `async void` or an unobserved `Task` with no error handling behind it.",
303
+ "outputSha256": "aeaa6240e42ef6fa1262831857be0654ffed24ab9bc703423900e9f7430e38e6",
304
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
305
+ "deterministic": [],
306
+ "judge": {
307
+ "verdict": "pass",
308
+ "reason": "Criterion 1: 'The method is Task-returning, never async void' and signature `public async Task SendOrderShippedEmailAsync`. Criterion 2: concrete mechanisms — `_ = _notificationQueue.EnqueueAsync(...)`, Channel-backed BackgroundService/IHostedService worker with try/catch logging. Criterion 3: 'any exception it throws ... escapes to the thread pool and crashes the process instead of reaching your caller or your logger.' No fail criterion met."
309
+ },
310
+ "passed": true
311
+ },
312
+ {
313
+ "output": "The right shape here is: keep `PlaceOrder` awaiting only the actual order work, and hand the *sending* of the email to a background mechanism that the request does not block on. The important part is that \"don't make the caller wait\" does **not** mean `async void`.\n\n## The wrong shapes (and why)\n\n```csharp\n// WRONG: async void, not an event handler\npublic async void PlaceOrder(Order order)\n{\n await _repo.SaveAsync(order);\n await _email.SendOrderShippedAsync(order); // caller can't observe this; an exception here crashes the process\n}\n```\n\n```csharp\n// WRONG: sync-over-async / fire-and-forget on a raw Task\npublic Task PlaceOrder(Order order)\n{\n _repo.Save(order);\n _ = _email.SendOrderShippedAsync(order); // unobserved exception; may be killed when the request scope ends\n return Task.CompletedTask;\n}\n```\n\n`async void` can't be awaited or caught. A bare `_ = someTask` swallows failures and, in a web host, the DI scope and the `HttpContext` may be torn down underneath the still-running task — so the email may just never send, silently.\n\n## The right shape: return before the send, dispatch it durably\n\nThe method that the caller awaits should only await the order work. The email goes out through a background work mechanism, with a cancellation token that belongs to the *dispatcher*, not the request.\n\n```csharp\n// A background queue abstraction the service depends on.\npublic interface IBackgroundTaskQueue\n{\n ValueTask EnqueueAsync(Func<CancellationToken, Task> workItem, CancellationToken ct = default);\n}\n```\n\n```csharp\npublic sealed class OrderService(\n IOrderRepository repo,\n IBackgroundTaskQueue queue)\n{\n // The caller awaits only the save, not the email.\n public async Task PlaceOrderAsync(Order order, CancellationToken ct)\n {\n ArgumentNullException.ThrowIfNull(order);\n\n await repo.SaveAsync(order, ct);\n\n // Enqueue the send; caller does NOT wait on the SMTP/HTTP round-trip.\n await queue.EnqueueAsync(\n workItem: async token => await SendShippedEmailAsync(order, token),\n ct: ct);\n }\n\n private async Task SendShippedEmailAsync(Order order, CancellationToken ct)\n {\n // Whatever the real transport is -- SMTP client, HTTP API, etc.\n await _email.SendOrderShippedAsync(order, ct);\n }\n}\n```\n\nDepending on the project, `IBackgroundTaskQueue` is one of:\n\n- `IHostedService`/`BackgroundService`-backed queue (in-process, dies with the app — fine for best-effort notifications), or\n- a durable one: `IHostedService` draining a table, a channel consumed by a worker, or an outbox/`Hangfire`-style job store if the email must survive a crash (the correct choice when delivery actually matters).\n\nIf you cannot add a queue abstraction, the minimum correct in-process form is a scoped factory plus a hosted processor — but **do not** capture the `Scoped` email client or `DbContext` in a `Singleton` field. Resolve per use:\n\n```csharp\nusing var scope = _scopeFactory.CreateScope();\nvar emailClient = scope.ServiceProvider.GetRequiredService<IEmailClient>();\nawait emailClient.SendOrderShippedAsync(order, ct);\n```\n\n## The rules this follows\n\n- Keep the public method `Task`-returning (or `ValueTask` when the queue enqueue is genuinely hot and allocation-sensitive); never `async void` unless it is a true event handler.\n- No `.Result`, `.Wait()`, or `.GetAwaiter().GetResult()` — that's sync-over-async and can deadlock.\n- Pass the request's `CancellationToken` to the order work; give the background send its own token so it survives request cancellation.\n- Whatever resource the send touches (SMTP client, HTTP client, `DbContext`) must be `using`/`await using`'d or owned by the scope that resolves it — no leaking into a longer-lived singleton.\n- If delivery must be guaranteed, the queue must be durable (outbox/table/job store), not an in-memory channel.\n\n## Verify before calling it done\n\n- `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all pass.\n- `PlaceOrderAsync` awaits only repo/queue work; the email send is never on the awaited path.\n- No `async void` in the change (grep for it).\n- No `.Result`/`.Wait()`/`.GetAwaiter().GetResult()` introduced.\n- Any `Scoped` dependency reached from a `Singleton`-lifetime background processor goes through `IServiceScopeFactory`, not a captured field.\n\nOne caveat worth stating plainly: \"don't make the caller wait\" and \"the email is guaranteed to send\" are different requirements. The `IBackgroundTaskQueue` above satisfies the first. If you also need the second, that is an outbox/durable-queue design decision, not a C# async decision, and it should be made explicitly rather than assumed from the fire-and-forget pattern.",
314
+ "outputSha256": "ee332fe48351e5073d4b25797ca46fcde90c929094805f78e289a0bd6f65b421",
315
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
316
+ "deterministic": [],
317
+ "judge": {
318
+ "verdict": "pass",
319
+ "reason": "Criterion 1: states keep public method Task-returning, 'never async void unless it is a true event handler'. Criterion 2: concrete mechanism — IBackgroundTaskQueue.EnqueueAsync with IHostedService/BackgroundService worker, plus IServiceScopeFactory fallback. Criterion 3: 'async void can't be awaited or caught... an exception here crashes the process'. No fail criterion: async void only warned against."
320
+ },
321
+ "passed": true
322
+ },
323
+ {
324
+ "output": "Short answer: don't try to express \"fire and forget\" inside the `OrderService` method signature itself — make the email send a properly-tracked background operation and keep `PlaceOrder` returning its `Task` normally. Here's the shape I'd use.\n\n## The problem with the obvious approach\n\nThe tempting version is:\n\n```csharp\npublic void PlaceOrder(Order order) // or async void\n{\n // ... persist order ...\n _ = SendShippedEmailAsync(order); // fire and forget, swallowed Task\n}\n```\n\nThat's wrong for two independent reasons:\n\n- If `PlaceOrder` is `async void`, an exception thrown by the email path can't be awaited or caught by the caller — it surfaces as an unhandled exception and can tear down the process.\n- `_ = SendShippedEmailAsync(order)` discards the `Task`, so a failure is an unobserved task exception — silent in production, visible only if something later inspects `TaskScheduler.UnobservedTaskException`.\n\n`PlaceOrder` should still return `Task` and still be awaited by its caller; \"don't make the caller wait\" is solved by decoupling the *email work*, not by making `PlaceOrder` synchronous and sneaky.\n\n## Recommended shape\n\nSplit the two concerns: `PlaceOrder` does the transactional work, and the notification is handed to a background queue that is actually tracked and drainable.\n\n```csharp\npublic interface IOrderNotifier\n{\n ValueTask EnqueueShippedAsync(Order order, CancellationToken cancellationToken = default);\n}\n\npublic sealed class OrderService\n{\n private readonly IOrderRepository _orders;\n private readonly IOrderNotifier _notifier;\n private readonly ILogger<OrderService> _logger;\n\n public OrderService(\n IOrderRepository orders,\n IOrderNotifier notifier,\n ILogger<OrderService> logger)\n {\n ArgumentNullException.ThrowIfNull(orders);\n ArgumentNullException.ThrowIfNull(notifier);\n ArgumentNullException.ThrowIfNull(logger);\n _orders = orders;\n _notifier = notifier;\n _logger = logger;\n }\n\n public async Task PlaceOrderAsync(Order order, CancellationToken cancellationToken = default)\n {\n ArgumentNullException.ThrowIfNull(order);\n\n await _orders.SaveAsync(order, cancellationToken).ConfigureAwait(false);\n\n // The caller awaits PlaceOrderAsync, but this returns as soon as the\n // notification is *accepted* for delivery -- not when it is sent.\n await _notifier\n .EnqueueShippedAsync(order, cancellationToken)\n .ConfigureAwait(false);\n }\n}\n```\n\nThe key property: `EnqueueShippedAsync` completes quickly (it hands the order to a channel/queue and returns), so `PlaceOrderAsync` doesn't block on SMTP/HTTP. The caller still `await`s `PlaceOrderAsync`, so any failure to *enqueue* is observed and propagates normally. Delivery failures are handled inside the background consumer, where they can be logged, retried, and dead-lettered.\n\n## The background consumer\n\nUse `BackgroundService` plus a `System.Threading.Channels` channel as the queue. This is in-process and dependency-free; swap the channel for a real broker (Azure Service Bus, RabbitMQ, SQS) if you need cross-process durability or at-least-once delivery.\n\n```csharp\npublic sealed class OrderNotifier : IOrderNotifier\n{\n private readonly Channel<Order> _channel =\n Channel.CreateBounded<Order>(new BoundedChannelOptions(capacity: 1000)\n {\n FullMode = BoundedChannelFullMode.Wait, // apply backpressure instead of dropping\n SingleReader = true,\n SingleWriter = false,\n });\n\n public ValueTask EnqueueShippedAsync(Order order, CancellationToken cancellationToken = default)\n => _channel.Writer.WriteAsync(order, cancellationToken);\n\n public IAsyncEnumerable<Order> ReadAllAsync(CancellationToken cancellationToken)\n => _channel.Reader.ReadAllAsync(cancellationToken);\n}\n\npublic sealed class OrderNotificationWorker : BackgroundService\n{\n private readonly OrderNotifier _notifier;\n private readonly IEmailSender _emailSender;\n private readonly ILogger<OrderNotificationWorker> _logger;\n\n public OrderNotificationWorker(\n OrderNotifier notifier,\n IEmailSender emailSender,\n ILogger<OrderNotificationWorker> logger)\n {\n _notifier = notifier;\n _emailSender = emailSender;\n _logger = logger;\n }\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var order in _notifier.ReadAllAsync(stoppingToken).ConfigureAwait(false))\n {\n try\n {\n await _emailSender.SendShippedAsync(order, stoppingToken).ConfigureAwait(false);\n }\n catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)\n {\n break; // shutting down\n }\n catch (Exception ex)\n {\n // Delivery failed. Log it and decide: retry, dead-letter, or alert.\n _logger.LogError(ex, \"Failed to send shipped email for order {OrderId}\", order.Id);\n }\n }\n }\n}\n```\n\n## DI registration\n\n```csharp\nbuilder.Services.AddSingleton<OrderNotifier>();\nbuilder.Services.AddSingleton<IOrderNotifier>(sp => sp.GetRequiredService<OrderNotifier>());\nbuilder.Services.AddHostedService<OrderNotificationWorker>();\n\nbuilder.Services.AddScoped<IOrderRepository, OrderRepository>();\nbuilder.Services.AddScoped<OrderService>();\n```\n\n`OrderNotifier` is a `Singleton` because it owns the channel that must outlive any single request. The worker is a hosted service resolved from the root scope — so if the email path ever needs a `DbContext` or other scoped dependency, resolve it per-message through `IServiceScopeFactory` inside the loop rather than injecting it into the worker's constructor. Capturing a scoped `DbContext` in a singleton's field pins one request's instance for the app lifetime.\n\n## Decisions that matter here\n\n- **`Task` vs `ValueTask` for `EnqueueShippedAsync`.** A `ValueTask` is right if enqueueing usually completes synchronously (unbounded channel, or a channel with room) — it avoids the allocation on the fast path. Don't `await` a `ValueTask` twice, and don't hold one for later; if that's a risk, just return `Task`.\n- **Bounded vs unbounded channel.** Unbounded hides overload and eventually OOMs. Bounded with `Wait` applies backpressure — `PlaceOrderAsync`'s enqueue await will slow down when the queue backs up, which is honest. Bounded with `DropWrite`/`DropOldest` fails silently; only choose that if losing a notification is genuinely acceptable, and log the drop.\n- **Durability.** The in-process channel loses queued orders on restart. If an unsent shipped email is not allowed to be lost, persist the intent in the same transaction as the order (an outbox row) and have the worker drain the outbox — that's the transactional-outbox pattern, and the only way to make \"order saved\" and \"email will be sent\" atomic.\n\n## What not to do\n\n- Don't make `PlaceOrder` `async void` or `void`. Its caller can't observe its completion or its failures.\n- Don't use `_ = SendShippedEmailAsync(order)`. The `Task` is discarded; failures are unobserved.\n- Don't block on the send with `.Result`/`.Wait()` to \"avoid async\" — that's sync-over-async and can deadlock.\n- Don't reach for `ThreadPool.QueueUserWorkItem` or a raw `Task.Run` for this. `Task.Run` around I/O just moves the work to a pool thread with no backpressure, no retry, no shutdown draining, and still-discardable failures.\n\n## Verification\n\nBefore calling this done:\n\n- `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes` all pass.\n- `OrderService.PlaceOrderAsync` is `async Task` (not `async void`) and its caller `await`s it.\n- `IOrderNotifier.EnqueueShippedAsync` is `Task`/`ValueTask`-returning and threads `CancellationToken`.\n- Every `IDisposable`/`IAsyncDisposable` local (channel, scope, sender) is wrapped in `using`/`await using` or owned correctly.\n- The worker catches exceptions per message so one failed send doesn't kill the loop, and honors `stoppingToken` for graceful shutdown.\n- If losing a queued notification on restart is unacceptable, the email intent is written to an outbox in the same transaction as the order — not held only in memory.",
325
+ "outputSha256": "776d7c98d752e4e3653458deefe780c70cd47c70f265520633287313499f05eb",
326
+ "promptSha256": "79f77c44c067d970c260658d280afa5532d1a103022d8300c4dfd53a24b518c3",
327
+ "deterministic": [],
328
+ "judge": {
329
+ "verdict": "pass",
330
+ "reason": "Criterion 1: answer keeps PlaceOrderAsync as 'async Task' and warns 'Don't make PlaceOrder async void or void'. Criterion 2: concrete mechanism — IOrderNotifier.EnqueueShippedAsync writes to a bounded Channel, drained by OrderNotificationWorker (BackgroundService), with per-message try/catch logging. Criterion 3: states async void exception 'can't be awaited or caught by the caller — it surfaces as an unhandled exception and can tear down the process'. No fail criterion: async void only warned against."
331
+ },
332
+ "passed": true
333
+ }
334
+ ]
335
+ },
336
+ {
337
+ "id": "scoped-into-singleton",
338
+ "kind": "behavior",
339
+ "prompt": "I have a Singleton `OrderCacheService` in my ASP.NET Core app that occasionally needs to read from the database. Can I just inject my Scoped `OrderDbContext` into its constructor?",
340
+ "strictness": "high",
341
+ "trials": 10,
342
+ "passes": 10,
343
+ "passRate": 1,
344
+ "passAtK": 1,
345
+ "grader": "judge",
346
+ "status": "ran",
347
+ "trialRecords": [
348
+ {
349
+ "output": "No — and this is one of the sharpest failure modes in ASP.NET Core DI, so it is worth understanding exactly why before reaching for a different shape.\n\n## Why constructor-injecting a Scoped service into a Singleton is wrong\n\nA Singleton is resolved exactly once, from the root `IServiceProvider`, and lives for the lifetime of the application. A `DbContext` is registered Scoped, meaning it is *supposed* to be created per request (technically, per scope) and disposed when that scope ends.\n\nIf you inject `OrderDbContext` into a Singleton's constructor, one of two things happens:\n\n1. **You get an `InvalidOperationException` at startup or first resolution.** With `ValidateScopes` enabled — which is the default in the Development environment, and which you should enable everywhere — the container refuses to resolve a Scoped service from the root provider and throws: *\"Cannot consume scoped service 'OrderDbContext' from singleton 'OrderCacheService'.\"* This is the good outcome, because it fails fast.\n\n2. **You disable scope validation (or are in an environment where it's off) and you get silent, catastrophic behavior instead.** The Singleton captures the very first `DbContext` instance ever resolved, from the root scope, and reuses it for the lifetime of the process. That `DbContext` is not thread-safe. Concurrent requests hitting your cache service will race on the same change tracker, produce \"A second operation was started on this context instance before a previous operation completed,\" accumulate tracked entities forever until memory is exhausted, and serve progressively staler data. It also never gets disposed at the right time.\n\nSo the short answer: no, not via constructor injection.\n\n## What to do instead\n\nThe correct pattern is **resolve the scoped dependency per-use, inside a fresh scope**, using `IServiceScopeFactory`. Inject `IServiceScopeFactory` (which is itself a Singleton, so this is legal) and create a scope each time you actually need the database.\n\n```csharp\npublic sealed class OrderCacheService : IOrderCacheService\n{\n private readonly IServiceScopeFactory _scopeFactory;\n private readonly IMemoryCache _cache; // or your own cache field\n\n public OrderCacheService(IServiceScopeFactory scopeFactory, IMemoryCache cache)\n {\n _scopeFactory = scopeFactory;\n _cache = cache;\n }\n\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken cancellationToken = default)\n {\n if (_cache.TryGetValue(id, out Order? cached))\n {\n return cached;\n }\n\n await using var scope = _scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n var order = await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, cancellationToken);\n\n if (order is not null)\n {\n _cache.Set(id, order, TimeSpan.FromMinutes(5));\n }\n\n return order;\n }\n}\n```\n\nKey points in that snippet:\n\n- **`CreateAsyncScope()`** rather than `CreateScope()` — `DbContext` implements `IAsyncDisposable`, and the async overload disposes it correctly on the async path. It returns an `AsyncServiceScope` that you dispose with `await using`.\n- **`AsNoTracking()`** for a read-only cache path — you are not going to mutate and save these entities, so skip the change tracker entirely. This also sidesteps an entire class of \"the tracked instance is now stale/shared\" bugs.\n- **A fresh `DbContext` per operation.** Do not stash the resolved `db` in a field. Every call gets a clean context with its own scope and its own change tracker, which is exactly what you want.\n- **Pass the `CancellationToken` through** to the EF call so a cancelled request does not keep a DB round-trip alive.\n- **Register the Singleton normally:** `services.AddSingleton<IOrderCacheService, OrderCacheService>();` — no special lifetime gymnastics needed once the `DbContext` is resolved inside a scope.\n\n## A design question worth pausing on\n\nIf `OrderCacheService` reaches for the database \"occasionally,\" ask whether the caching and the data access belong in the same type. A cleaner split is often:\n\n- A Scoped `OrderRepository`/`OrderService` that owns the `DbContext` and does the querying, and\n- A Singleton cache that either takes a `Func<CancellationToken, Task<Order?>>` factory or a scope factory, and treats the repository as something to invoke inside a scope.\n\nThat keeps the Singleton free of data-access concerns and keeps the `DbContext` where it naturally belongs — inside a per-request or per-operation scope. It also makes the cache trivially unit-testable: you inject a fake factory instead of fighting the DI container.\n\nThe one thing you must not do is break the lifetime chain by capturing a Scoped dependency — directly or transitively — in a Singleton's constructor. `IServiceScopeFactory` is the sanctioned escape hatch, and it is a Singleton itself precisely so this pattern is available.",
350
+ "outputSha256": "51f3a7552603ca677ca6f75e7ceb8617e65a2f76acca705a823889a81d4dae1f",
351
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
352
+ "deterministic": [],
353
+ "judge": {
354
+ "verdict": "pass",
355
+ "reason": "Answer plainly says no to constructor injection (\"So the short answer: no, not via constructor injection\"). It names the fix: inject IServiceScopeFactory and call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per use, shown in code. It states the consequence: the Singleton captures the first DbContext from the root scope and reuses it for the process lifetime, not fresh per operation."
356
+ },
357
+ "passed": true
358
+ },
359
+ {
360
+ "output": "No — that's exactly the capture-a-Scoped-into-a-Singleton trap this skill's rules call out, and it fails loudly at startup rather than silently, which is at least in your favor.\n\n**Why it doesn't work:** a Singleton is constructed once for the app's lifetime, and its constructor dependencies are resolved once too. ASP.NET Core's DI container will throw on this specific shape:\n\n```\nInvalidOperationException: Cannot consume scoped service 'OrderDbContext'\nfrom singleton 'OrderCacheService'.\n```\n\nThe container detects the lifetime mismatch and refuses to build. Even if you defeated that check (e.g. by resolving eagerly from the root provider), you'd be capturing one `DbContext` instance for the entire application lifetime — a `DbContext` is not thread-safe, tracks entities it has seen, and grows its change tracker indefinitely. Concurrent requests calling into your cache would hit the same instance from multiple threads.\n\n**The correct pattern** — resolve per-use through a scope factory:\n\n```csharp\npublic sealed class OrderCacheService(IServiceScopeFactory scopeFactory)\n{\n public async Task<Order?> GetOrderAsync(int id, CancellationToken ct = default)\n {\n await using var scope = scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n return await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n }\n}\n```\n\nNotes on that:\n\n- `CreateAsyncScope()` + `await using` so a `DbContext` (which is `IAsyncDisposable`) is disposed asynchronously and the scope's tracked state is torn down after every call. Don't use the synchronous `CreateScope()` in an async path.\n- `AsNoTracking()` on a read-only cache lookup — you gain nothing from the change tracker and pay its per-entity cost.\n- The container already disposes scoped services it created; the explicit `await using` here is what releases the scope you created yourself.\n\n**Lifetimes, for the record:**\n\n| Lifetime | Constructed | Disposed |\n|---|---|---|\n| Singleton | Once per app | At app shutdown |\n| Scoped | Once per request/scope | At scope end |\n| Transient | Every resolve | At owning scope end |\n\n`DbContext` is registered Scoped (via `AddDbContext`). A Singleton may only depend on other Singletons.\n\n**If the reads are actually hot**, injecting `IDbContextFactory<OrderDbContext>` and calling `CreateDbContextAsync()` per operation is the lighter-weight equivalent — it skips building a scope just to get one service. Reach for the factory when the cache service only ever needs a `DbContext`; use `IServiceScopeFactory` when you need to resolve several scoped services together.\n\nOne design question worth asking before implementing: if `OrderCacheService` is reading from the DB on the miss path anyway, is the \"cache\" pulling its weight, or is it a Singleton whose main job is holding an `IMemoryCache`? If the latter, keep the DB access in a separate Scoped `OrderRepository` and let the Singleton own only the cache and a scope factory to reach the repository.",
361
+ "outputSha256": "ccfad00bf6ad9cecc876e8f8dbfa1a24d4326e5891932ef18ad915ab55688e73",
362
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
363
+ "deterministic": [],
364
+ "judge": {
365
+ "verdict": "pass",
366
+ "reason": "Answer plainly says no to injecting Scoped OrderDbContext into Singleton constructor ('capture-a-Scoped-into-a-Singleton trap'). Concrete fix shown: inject IServiceScopeFactory and call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per use (code sample). Consequence stated: 'capturing one DbContext instance for the entire application lifetime' instead of fresh per operation. No fail criterion met."
367
+ },
368
+ "passed": true
369
+ },
370
+ {
371
+ "output": "No — not safely. That's the exact case the checklist calls out under Red Flags: injecting a Scoped `DbContext` into a Singleton constructor captures the *first* `DbContext` instance the container ever resolves for that slot and reuses it for the entire application lifetime. A `DbContext` is not thread-safe and is designed to be short-lived (one unit of work); sharing one instance across concurrent requests from a Singleton will produce intermittent, hard-to-reproduce failures — stale tracked entities, cross-request data leaking, and `InvalidOperationException: A second operation started on this context` under load. Depending on the validator, the container may also throw at startup or fail a scope-validation check because a Singleton may not consume a Scoped service directly.\n\nSo: no, do not inject it into the constructor.\n\nThe fix is to keep the Singleton, drop the constructor-injected `DbContext`, and resolve one per use inside a scope you create:\n\n```csharp\npublic sealed class OrderCacheService\n{\n private readonly IServiceScopeFactory _scopeFactory;\n\n public OrderCacheService(IServiceScopeFactory scopeFactory)\n {\n ArgumentNullException.ThrowIfNull(scopeFactory);\n _scopeFactory = scopeFactory;\n }\n\n public async Task<Order?> GetOrderAsync(int id, CancellationToken ct)\n {\n await using var scope = _scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n return await db.Orders\n .AsNoTracking()\n .FirstOrDefaultAsync(o => o.Id == id, ct);\n }\n}\n```\n\nNotes on this shape:\n\n- `IServiceScopeFactory` is itself a Singleton, so injecting it into a Singleton is valid.\n- `CreateAsyncScope()` (not `CreateScope()`) with `await using` matters if your `DbContext` provider or anything downstream is `IAsyncDisposable`; the `await using` guarantees the scope — and therefore the `DbContext` — is disposed at the end of the call.\n- Pull the `DbContext` out of `scope.ServiceProvider` on every call; never cache it in a field.\n- `.AsNoTracking()` is appropriate here since a read-only cache path doesn't need change tracking, and it avoids the context quietly accumulating tracked entities across reads.\n- One `DbContext` per method call is one connection/unit-of-work — if you're doing multiple reads for one logical operation, create the scope once per operation, not once per query.\n\nTwo alternatives worth weighing before you commit to this design:\n\n1. **Reconsider the lifetime.** If `OrderCacheService` mostly serves cached data and only occasionally hits the database, the scope-per-read approach above is right. But if it's effectively doing database work on nearly every call, the cache likely shouldn't be a Singleton at all — make it Scoped and inject the `DbContext` normally, letting the container manage the scope for you.\n\n2. **Split the concern.** A cleaner structure is often a Singleton cache holding only the in-memory state, plus a separate Scoped `IOrderReader` that owns the `DbContext`. The Singleton depends on a factory/delegate that produces a Scoped reader per read, keeping the lifetime mismatch explicit rather than buried in the cache service.\n\nFor the read itself, keep the filtering and projection in the `IQueryable` chain (`.Where`, `.Select`) so it translates to SQL, and materialize with `FirstOrDefaultAsync`/`SingleOrDefaultAsync`/`ToListAsync` at the point you actually need the results — same rule as anywhere else in the codebase.",
372
+ "outputSha256": "7fb24da032eca3ff577bc82b8d4185cb40f1e71b09fd8d8587a9f7b96bd3130c",
373
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
374
+ "deterministic": [],
375
+ "judge": {
376
+ "verdict": "pass",
377
+ "reason": "Answer plainly says no to injecting the Scoped DbContext into the Singleton constructor ('no, do not inject it into the constructor'). It names the concrete fix: inject IServiceScopeFactory and call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per use, shown in code. It states the consequence: the first DbContext instance is captured and reused for the entire application lifetime."
378
+ },
379
+ "passed": true
380
+ },
381
+ {
382
+ "output": "No — that will break at runtime.\n\nA Singleton is constructed once, from the root `IServiceProvider`, with no per-request scope. When you inject a Scoped `OrderDbContext` into its constructor, the DI container throws `InvalidOperationException: Cannot consume scoped service 'OrderDbContext' from singleton 'OrderCacheService'` at startup (validate-scopes is on by default in Development). If you disable that validation, the failure mode gets worse, not better: you capture the *first* resolved `DbContext` instance for the entire lifetime of the app. It accumulates tracked entities forever (unbounded memory growth), and any concurrent request touching that shared instance will hit EF Core's \"A second operation was started on this context instance\" error because `DbContext` is not thread-safe.\n\nThe fix is to not depend on the context at all — depend on the scope factory and create a scope per database read:\n\n```csharp\npublic sealed class OrderCacheService(\n IServiceScopeFactory scopeFactory,\n IMemoryCache cache) : IOrderCacheService\n{\n public async Task<Order?> GetOrderAsync(int id, CancellationToken ct = default)\n {\n if (cache.TryGetValue(id, out Order? cached))\n return cached;\n\n await using var scope = scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n var order = await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n\n if (order is not null)\n cache.Set(id, order, TimeSpan.FromMinutes(5));\n\n return order;\n }\n}\n```\n\nKey points:\n\n- `IServiceScopeFactory` is itself a Singleton, so injecting it is legal and gives you a fresh scope (and therefore a fresh `DbContext`) per call.\n- `CreateAsyncScope()` + `await using` disposes the scope's services (including the `DbContext`) correctly; the sync `CreateScope()` + `using` also works if you never resolve an `IAsyncDisposable` from it.\n- `.AsNoTracking()` is important here — you're reading into a cache, not mutating, so skip the change tracker.\n- Thread `CancellationToken` through so a cancelled request doesn't leave a query running.\n\nIf reads turn out to be frequent and the cache hit rate is high, this is fine. If your Singleton is doing database work on nearly every call, that's a signal the caching strategy is wrong and you may want a Scoped service plus a separate Singleton cache that holds only immutable/poco data (never the context, and ideally not tracked entities either — projecting into a plain record before caching avoids serving stale tracked state).",
383
+ "outputSha256": "66132cf8984c7e6845be6c1509d2847b8e5f645fd029595340fd92910df345ff",
384
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
385
+ "deterministic": [],
386
+ "judge": {
387
+ "verdict": "pass",
388
+ "reason": "Answer says no (\"that will break at runtime\") and explains capturing the scoped DbContext into the singleton is wrong. Fix: inject IServiceScopeFactory and call scopeFactory.CreateAsyncScope() to resolve a fresh OrderDbContext per call, shown in code. Consequence: \"capture the first resolved DbContext instance for the entire lifetime of the app.\" All criteria met."
389
+ },
390
+ "passed": true
391
+ },
392
+ {
393
+ "output": "No — not directly. This is exactly the capture scenario Rule 4 and the Red Flags table call out.\n\n## Why injecting it directly breaks\n\nA Singleton is constructed once, at app startup (or first resolution), and lives for the entire application lifetime. A Scoped `DbContext` is meant to live for one request's scope. If you inject the Scoped `DbContext` into the Singleton's constructor:\n\n- The container resolves the `DbContext` **once**, from whatever scope was ambient at Singleton construction, and hands that same instance to the Singleton forever.\n- Every subsequent request that uses `OrderCacheService` reuses that first `DbContext`.\n- For EF Core specifically this is worse than a stale-data bug: `DbContext` is not thread-safe, so concurrent requests hitting your Singleton will call into the same context simultaneously and you get `InvalidOperationException: A second operation was started on this context instance before a previous operation completed.`\n- It also grows the change tracker without bound, since it never gets reset per-request, and it can hold on to entities/db connections far past any request.\n\nAnd in the common case, ASP.NET Core will actually *refuse* this at startup: with `ValidateScopes` / `ValidateOnBuild` enabled (on by default in Development), resolving a Scoped service from the root provider throws `InvalidOperationException: Cannot consume scoped service 'OrderDbContext' from singleton 'OrderCacheService'`. So you may not even reach the runtime bug — the app fails to build the container.\n\n## What to do instead — options in rough order of preference\n\n**1. Use `IServiceScopeFactory` and create a scope per DB read.** This is the direct answer to \"a Singleton needs a Scoped service.\" The Singleton stays a Singleton and owns no `DbContext`; it creates a fresh scope, resolves the context inside it, does the work, and disposes the scope.\n\n```csharp\npublic sealed class OrderCacheService(IServiceScopeFactory scopeFactory)\n{\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)\n {\n await using var scope = scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n return await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n // scope disposed here -> DbContext disposed, change tracker reset\n }\n}\n```\n\nNote this solves the lifetime problem *and* the practical symptom: each DB read gets its own context and its own connection, no shared mutable state across requests. Use `CreateAsyncScope()` (not `CreateScope()`) so it participates in async disposal; if your target framework is older, `CreateScope()` still works but you should prefer the async overload when available.\n\n**2. Ask whether the caching belongs in a Singleton at all.** If `OrderCacheService` reads the database on most operations, the \"cache\" is really a data service and arguably should be Scoped itself, wrapping a `DbContext` injected normally, with caching delegated to something explicitly shared (see #3). Singletons are for genuinely stateless, thread-safe, long-lived things. If the only reason it's a Singleton is \"I wanted the cache to survive requests,\" the cache should be the Singleton, not the DB accessor.\n\n**3. Split the newing from the caching into two services.** A Scoped `OrderService` (injects `OrderDbContext` normally) does the reads; a Singleton cache (e.g. backed by `IMemoryCache` with an expiration, or keyed with `IChangeToken`) holds the data. The request-scoped path does DB work, and the shared path holds only immutable cached values. This is usually the cleanest end state and avoids the scope-creation dance entirely.\n\n## A related trap even when you do inject something\n\nIf you inject `IServiceScopeFactory` (fine — it's a Singleton) vs. injecting something like `IServiceProvider` resolved from the root and then calling `GetRequiredService<OrderDbContext>()` on it, you get the same broken captured context, just deferred. Always go through `CreateScope()`/`CreateAsyncScope()` so the context is resolved inside a scope that gets disposed.\n\n## If you use option 1, keep these invariants\n\n- Never store the `DbContext` (or any entity tracked by it) as a field on the Singleton. Resolve it inside the method, use it, dispose the scope. Rule 4's \"resolve it per-use via `IServiceScopeFactory`\" is exactly this.\n- `await using var scope` (or a `using` block) — the scope is `IAsyncDisposable`, and leaking scopes leaks `DbContext`s and connections.\n- Thread the `CancellationToken` through to the EF Core call, per Step 3.4, so a cancelled request doesn't leave the read running.\n- If the data is safe to cache, cache *values* (detached DTOs / `AsNoTracking()` results), never the tracked instance, and think about invalidation.\n\n## Quick decision\n\n- Just need periodic DB reads from a Singleton → **`IServiceScopeFactory` + `CreateAsyncScope()` per read** (option 1).\n- Most operations hit the DB → **make the data-access service Scoped; keep only the cache Singleton** (options 2/3).\n- Direct constructor injection of the Scoped context → **don't**; it either fails container validation at startup or silently corrupts state under concurrency.\n\nBefore reporting done, verify with the skill's checklist: `dotnet build` / `dotnet test` / `dotnet format --verify-no-changes` pass, no `async void`, and no newly captured Scoped-into-Singleton state. A container validation failure at startup is a good failure — it's the framework catching exactly this mistake.",
394
+ "outputSha256": "c99385254dd3c54f5885dc6cdb91f2e63d45a2a244385a15357391047b989489",
395
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
396
+ "deterministic": [],
397
+ "judge": {
398
+ "verdict": "pass",
399
+ "reason": "Answer plainly says 'No — not directly' and that direct constructor injection is wrong (criterion 1). It names the concrete fix: inject IServiceScopeFactory and call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per read, with code shown (criterion 2). It states the consequence: the container resolves the DbContext once and hands that same instance to the Singleton forever, reused across requests (criterion 3). No fail criterion holds."
400
+ },
401
+ "passed": true
402
+ },
403
+ {
404
+ "output": "No — injecting a Scoped `OrderDbContext` into a Singleton's constructor is the classic captive-dependency bug. Here's why and what to do instead.\n\n## Why this breaks\n\nDI resolves the Singleton exactly once, at first request after startup. If its constructor takes `OrderDbContext`, the container is forced to resolve that `DbContext` at that moment, using whatever scope is active then — usually the root/application scope or the very first request's scope. From then on, your Singleton holds that one `DbContext` instance for the entire lifetime of the process.\n\nConsequences:\n\n- **One `DbContext` across all concurrent requests.** `DbContext` is not thread-safe. Two requests touching the cache at once will corrupt its internal change tracker, or throw `InvalidOperationException: A second operation was started on this context...`.\n- **Tracking accumulates forever.** Every entity ever loaded stays tracked by that single context, so memory grows unbounded and query results can return stale, cached-in-the-tracker entities.\n- **Disposal mismatch.** The `DbContext` is disposed when its *scope* ends (end of the first request), but your Singleton still holds a reference and will try to use a disposed context — `ObjectDisposedException` on the next cache miss.\n- **Connection lifetime.** The underlying DB connection gets held as long as the context lives, exhausting the pool.\n\nDepending on your DI container configuration, ASP.NET Core may also throw `InvalidOperationException: Cannot consume scoped service 'OrderDbContext' from singleton 'OrderCacheService'` at startup via the scope-validation check — which is the container trying to save you from exactly this.\n\n## What to do instead\n\n### Option 1: Resolve a scope per operation via `IServiceScopeFactory` (recommended for your case)\n\nYour Singleton takes `IServiceScopeFactory`, and each cache-miss opens a short-lived scope, resolves the context inside it, does the read, and lets the scope dispose it:\n\n```csharp\npublic sealed class OrderCacheService(\n IServiceScopeFactory scopeFactory,\n IMemoryCache cache) : IOrderCacheService\n{\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)\n {\n if (cache.TryGetValue(id, out Order? cached))\n return cached;\n\n await using var scope = scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n var order = await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n\n if (order is not null)\n cache.Set(id, order, TimeSpan.FromMinutes(5));\n\n return order;\n }\n}\n```\n\nThe scope is created and disposed around each read, so the context is never captured, never shared across requests, and never outlives its scope. This is the pattern for any Singleton that occasionally needs Scoped dependencies (DbContext, repositories, unit-of-work).\n\n### Option 2: Make `OrderCacheService` Scoped\n\nIf the cache logic doesn't actually need to be a Singleton — e.g., it's really just a per-request read-through that happens to store into an injected `IMemoryCache` — register it as Scoped and inject the `DbContext` directly. The `IMemoryCache` stays a Singleton, which is fine: Singletons can depend on other Singletons. This is simpler, but only valid if nothing requires the service to survive across requests.\n\n### Option 3: Separate the cache from the data access\n\nKeep the Singleton as a pure in-memory cache (only depends on `IMemoryCache`), and have a Scoped service (with the `DbContext`) do the DB read and populate/consult the Singleton cache. Clean separation, no scope plumbing in the cached class.\n\n## Rule of thumb\n\n- A Singleton may only depend on other Singletons.\n- A Scoped may depend on Scoped or Singleton.\n- A Transient may depend on anything.\n- If a Singleton needs something Scoped, it must resolve it per-use through `IServiceScopeFactory`, never capture it in a field.\n\nGiven your description — \"occasionally needs to read from the database\" — Option 1 is almost certainly the right fit: the Singleton's job is caching, and the per-operation scope keeps the `DbContext` correctly short-lived and request-isolated. Also add `.AsNoTracking()` to those reads, since you're materializing into a cache and have no intention of updating the entities.",
405
+ "outputSha256": "31c4256e5cb215e92ece09c561f0403efd616a5409a9acb36810d8e43a2a4e03",
406
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
407
+ "deterministic": [],
408
+ "judge": {
409
+ "verdict": "pass",
410
+ "reason": "Answer plainly says injecting Scoped OrderDbContext into Singleton constructor is wrong ('classic captive-dependency bug'). Names concrete fix: inject IServiceScopeFactory and call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per read, with code shown. States consequence: Singleton holds that one DbContext instance for the entire lifetime of the process, not fresh per operation."
411
+ },
412
+ "passed": true
413
+ },
414
+ {
415
+ "output": "No — that's the classic captive dependency bug, and it's one of the red flags this skill calls out by name.\n\n## Why it's wrong\n\n`OrderCacheService` is registered as `Singleton`: one instance for the entire application lifetime. `OrderDbContext` is registered as `Scoped`: a fresh instance per HTTP request (or per manually created scope).\n\nIf you inject the `DbContext` into the singleton's constructor, the DI container resolves it exactly once — during the singleton's construction — and captures that single `DbContext` instance for as long as the app runs. Consequences:\n\n- **It outlives its scope.** The scope that owned that `DbContext` disposes it when the first request completes. Every subsequent request now reads from a disposed context, and you get `ObjectDisposedException` on the second request onward.\n- **It's shared across threads.** `DbContext` is not thread-safe. Two concurrent requests hitting the singleton simultaneously will corrupt the change tracker / throw `InvalidOperationException: A second operation was started on this context`.\n- **Change tracking leaks.** The context accumulates tracked entities forever, growing memory without bound.\n\nBecause `Microsoft.Extensions.DependencyInjection` only validates scopes when you explicitly opt in (`ValidateScopes` is on by default in Development via `WebApplicationBuilder`, but not always in Production), this often \"works\" until the first deployed load test and then fails intermittently and confusingly. If scope validation *is* on, you instead get a clean `InvalidOperationException: Cannot consume scoped service 'OrderDbContext' from singleton 'OrderCacheService'` at startup — which is the better outcome.\n\n## The fix: create a scope per unit of work\n\nInject `IServiceScopeFactory` (a singleton) instead, and resolve the `DbContext` from a fresh scope each time you actually need it. This is the pattern the skill requires: \"Never capture a `Scoped` service (like a `DbContext`) directly into a `Singleton`'s field — resolve it per-use via `IServiceScopeFactory`.\"\n\n```csharp\npublic sealed class OrderCacheService\n{\n private readonly IServiceScopeFactory _scopeFactory;\n private readonly IMemoryCache _cache;\n\n public OrderCacheService(IServiceScopeFactory scopeFactory, IMemoryCache cache)\n {\n _scopeFactory = scopeFactory;\n _cache = cache;\n }\n\n public async Task<Order?> GetOrderAsync(int id, CancellationToken ct = default)\n {\n if (_cache.TryGetValue<Order>(CacheKey(id), out var cached))\n {\n return cached;\n }\n\n // Fresh scope per read; the DbContext lives and dies inside this block.\n await using var scope = _scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n var order = await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n\n if (order is not null)\n {\n _cache.Set(CacheKey(id), order, TimeSpan.FromMinutes(5));\n }\n\n return order;\n }\n\n private static string CacheKey(int id) => $\"order:{id}\";\n}\n```\n\nKey points, all straight from the checklist:\n\n- `await using var scope = _scopeFactory.CreateAsyncScope();` — the scope is disposed (and the `DbContext` with it) at the end of the method. Use `CreateAsyncScope()` rather than `CreateScope()` so async disposal is honored.\n- The `DbContext` is a **local**, not a field. It is never captured into the singleton.\n- `AsNoTracking()` — this is a read-only path, so don't pay for change tracking.\n- `CancellationToken` is threaded through to the EF Core async call.\n\n## Consider an alternative design\n\nIf `OrderCacheService` only caches data read from the database, the cleaner shape is often a small **scoped** `OrderRepository` (which injects the `DbContext` normally) plus one of:\n\n- Make the *caller* scoped, and have the singleton depend only on an abstraction that itself creates scopes; or\n- Keep the singleton purely for the cache, and do the DB read in a scoped service that writes into the shared `IMemoryCache`/`IDistributedCache` (which is itself a safe singleton).\n\n`IServiceScopeFactory` is the quick, correct fix if you want to keep the current shape; a split cache-vs-data-access design is worth considering if this service grows. Either way, the rule is the same: a singleton never holds a `Scoped` reference directly.\n\n## Verification before you call it done\n\n- `dotnet build`, `dotnet test`, `dotnet format --verify-no-changes` all exit 0.\n- No `Scoped` (`DbContext`, repository, etc.) is stored in a `Singleton` field.\n- The scope used to resolve the `DbContext` is disposed via `await using`.\n- The read path is `AsNoTracking()` and takes a `CancellationToken`.\n- Run with scope validation on (`builder.Host.UseDefaultServiceProvider(o => o.ValidateScopes = true)`) to catch any other captive dependencies you may have introduced elsewhere.",
416
+ "outputSha256": "187624e958af38f9291ca6cee6c1a468097b696e47a24ced12cdf02ff60572b1",
417
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
418
+ "deterministic": [],
419
+ "judge": {
420
+ "verdict": "pass",
421
+ "reason": "Answer plainly says injecting the Scoped DbContext into the Singleton constructor is wrong ('captive dependency bug'), names the fix (inject IServiceScopeFactory, call CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per use, shown in code), and states the consequence: the DbContext is resolved once at construction and captured for the app's lifetime, outliving its scope. No fail criterion holds."
422
+ },
423
+ "passed": true
424
+ },
425
+ {
426
+ "output": "No — injecting a Scoped `OrderDbContext` into a Singleton's constructor is the exact capture problem this skill's rules call out, and ASP.NET Core will either throw at startup (if scope validation is on, which it is by default in Development) or, if validation is bypassed, silently hand your Singleton the *first* request's `DbContext` instance and reuse it for the app's entire lifetime.\n\nWhy that is a real bug and not just a style nit:\n\n- A `DbContext` is not thread-safe and is designed to live for one unit of work. A Singleton outlives every request, so concurrent requests would end up sharing one context — you get \"A second operation was started on this context before a previous operation completed,\" stale change-tracker state, and entities from one request leaking into another's results.\n- Even in single-threaded usage, the change tracker keeps accumulating tracked entities for the life of the process. Memory grows and SaveChanges starts writing things you never intended to save.\n- The connection is only released when the context is disposed, which for a captured context means never (until app shutdown).\n\n## The fix\n\nInject `IServiceScopeFactory` into the Singleton and create a scope per read:\n\n```csharp\npublic sealed class OrderCacheService(IServiceScopeFactory scopeFactory)\n{\n private readonly IServiceScopeFactory _scopeFactory = scopeFactory;\n\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct = default)\n {\n await using var scope = _scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n return await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n }\n}\n```\n\nNotes on the shape:\n\n- `CreateAsyncScope` / `await using` so the scope — and the `DbContext` inside it — is disposed asynchronously after the query. A plain `using` works too, but async disposal is the correct pairing for an async data access path.\n- `AsNoTracking()` because a throwaway per-call context gains nothing from the change tracker, and you don't want read results tracked.\n- Keep the `IQueryable` filtering and the `SingleOrDefaultAsync` materialization inside the scope; don't return a lazy query or a tracked entity out of the scope.\n- Keep the scope as short as the read: create it, do the query, dispose it. If you need several reads for one logical operation, that's one scope; don't hoist the `DbContext` into a field.\n\nIf `OrderCacheService` is really a cache, the more idiomatic design is for the Singleton to own only the cache, and to depend on a Scoped (or otherwise correctly-scoped) repository/service that does the DB read — injected where the read is *initiated* per request, with the result handed to the Singleton to store. Use `IServiceScopeFactory` when the Singleton itself must drive the read on its own schedule (a background refresh, an expiry callback); use plain constructor injection of a scoped dependency when the read is triggered by an incoming request.\n\nOne caveat before you ship: confirm the DI container isn't configured with `ValidateScopes = false` in your production host builder. If it is, this misconfiguration will *not* fail fast — it will just misbehave under load, which is far harder to diagnose than a startup exception.",
427
+ "outputSha256": "67def9224eac3cfaefb28c05b2561aa69a059806b14238af206addb83717268f",
428
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
429
+ "deterministic": [],
430
+ "judge": {
431
+ "verdict": "pass",
432
+ "reason": "Answer plainly says injecting Scoped OrderDbContext into Singleton constructor is wrong ('the exact capture problem'), names the fix (inject IServiceScopeFactory, CreateAsyncScope/CreateScope, GetRequiredService<OrderDbContext> per read, shown in code), and states the consequence: 'silently hand your Singleton the first request's DbContext instance and reuse it for the app's entire lifetime.' No fail criterion holds."
433
+ },
434
+ "passed": true
435
+ },
436
+ {
437
+ "output": "No — that's exactly the capture-the-Scoped-service-in-a-Singleton trap, and it's the one case where DI validation will fail at startup (or, worse, silently succeed in some setups) while still being broken at runtime.\n\n**Why it breaks**\n\n`OrderCacheService` is constructed once, for the lifetime of the application. Whatever `OrderDbContext` gets passed into its constructor is the instance created inside whatever scope happened to build the Singleton first — typically the very first request. From that point on:\n\n- Every request that calls into the cache reads from that one original `DbContext` instance, so all callers share one change tracker and one connection.\n- `DbContext` is not thread-safe. Concurrent requests calling into the Singleton simultaneously will hit race conditions on the same context (meaningful given that ASP.NET Core serves requests in parallel).\n- The context — and anything it holds open — is never disposed until the app shuts down.\n- If you ever enable `ValidateScopes` / `ValidateOnBuild` (they're on by default for the Development environment in .NET 6+), the container will actually throw on this at startup, which is the good outcome. If scope validation is off, you get the silent runtime corruption instead.\n\n**What to do instead**\n\nResolve the `DbContext` per-use inside a fresh scope, via `IServiceScopeFactory`:\n\n```csharp\npublic sealed class OrderCacheService(\n IServiceScopeFactory scopeFactory,\n IMemoryCache cache) : IOrderCacheService\n{\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct)\n {\n if (cache.TryGetValue(id, out Order? cached))\n return cached;\n\n await using var scope = scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n\n var order = await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n\n if (order is not null)\n cache.Set(id, order, TimeSpan.FromMinutes(5));\n\n return order;\n }\n}\n```\n\nA few things worth calling out about that shape:\n\n- **`CreateAsyncScope()` + `await using`**, not `CreateScope()`. The async variant lets the scope dispose anything implementing `IAsyncDisposable` correctly. Since `DbContext` supports async disposal, this is the right pairing for I/O-touching code.\n- **`AsNoTracking()`** because this is a pure read whose result goes into a cache — you don't want the change tracker accumulating entities in a context you're about to dispose anyway.\n- **`CancellationToken` threaded through** so a caller cancelling the request cancels the database read, per the skill's Step 3.3.\n- The scope is created and torn down *inside* the method, so each read gets its own short-lived, correctly-disposed context. That's the whole point.\n\n**Alternative worth considering**\n\nIf `OrderCacheService` is really just a caching decorator over a repository, the cleaner factoring is often to keep it Singleton-free-of-`DbContext` entirely and instead depend on `IOrderRepository` (a Scoped service) — but then the *caller* would have to be Scoped too, which contradicts the Singleton requirement. If the caching layer genuinely needs to outlive requests, `IServiceScopeFactory` as above is the standard, correct answer.\n\nThe reason this is a `Red Flags` entry rather than a footnote in this skill is that the failure mode is invisible in code review — the constructor injection *looks* identical to every other correctly-injected service in your app. The only thing distinguishing it is the lifetime mismatch, which is why you check the Singleton's dependencies against `Scoped`-lifetime types before picking `Singleton` in Step 2.",
438
+ "outputSha256": "dee543210b3f2be24f65c7198018830a5e323d8f0d1bd5ded9952ec5f14f4daf",
439
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
440
+ "deterministic": [],
441
+ "judge": {
442
+ "verdict": "pass",
443
+ "reason": "Answer plainly says no (\"that's exactly the capture-the-Scoped-service-in-a-Singleton trap\"), names the fix (inject IServiceScopeFactory, CreateAsyncScope()/CreateScope() per use, with code), and states the consequence (\"constructed once, for the lifetime of the application\", one shared context, never disposed until shutdown). No fail criterion holds."
444
+ },
445
+ "passed": true
446
+ },
447
+ {
448
+ "output": "No. That is the exact pattern the red-flag table calls out. A `Singleton` lives for the whole application lifetime, so its constructor runs once and captures the `OrderDbContext` instance that happened to be resolved at that moment. From then on you are reusing a single `DbContext` across every subsequent request, across every thread, for the life of the process. `DbContext` is not thread-safe, its change tracker grows unbounded, and it is holding a connection/scope that belongs to whatever request first triggered the resolution. You will get intermittent concurrency exceptions, stale cached entities, and — if you register it this way with the default `ValidateScopes`/`ValidateOnBuild` behavior — ASP.NET Core will actually throw at startup with a \"Cannot consume scoped service from singleton\" error, so in many configurations you cannot even get past `dotnet build`/first `RequestServices` resolution.\n\nThe fix is to give the singleton an `IServiceScopeFactory` (which is itself a singleton and is safe to inject) and create a short-lived scope each time it needs the database:\n\n```csharp\npublic sealed class OrderCacheService(IServiceScopeFactory scopeFactory)\n{\n private readonly IServiceScopeFactory _scopeFactory = scopeFactory;\n\n public async Task<Order?> GetOrderAsync(Guid id, CancellationToken ct = default)\n {\n // cache hit would short-circuit here\n await using var scope = _scopeFactory.CreateAsyncScope();\n var db = scope.ServiceProvider.GetRequiredService<OrderDbContext>();\n return await db.Orders\n .AsNoTracking()\n .SingleOrDefaultAsync(o => o.Id == id, ct);\n }\n}\n```\n\nKey points:\n\n- `IServiceScopeFactory` is registered as a singleton by the DI container itself, so injecting it into your singleton is legal and does not capture request-scoped state.\n- `CreateAsyncScope()` (rather than the sync `CreateScope()`) returns an `AsyncServiceScope` that supports `await using`, so disposal of async disposables (and `DbContext` is one) is awaited properly. Use the sync `CreateScope()`/`using` only if you have no async dispose path.\n- The `DbContext` is resolved per call and disposed when the scope ends, so each operation gets a fresh, correctly-scoped context.\n- Note the `AsNoTracking()` — for a read-through cache you almost certainly do not want change tracking; it avoids the tracker growing and reflects the read-only intent.\n- Also thread `CancellationToken` through, as the skill's Step 3 requires for I/O calls.\n\nAn alternative design, if the cache and the data access are logically separable, is to keep `OrderCacheService` as a singleton that depends only on a `Scoped`-friendly abstraction resolved per call, but the scope-factory approach above is the standard, well-understood pattern and avoids introducing a layer for its own sake.\n\nOne caveat: if you do this and also make the cache store `Order` instances returned from the context, prefer projecting into a plain record (`OrderDto`/`OrderSnapshot`) rather than holding on to tracked entities — returning entities that outlive their scope is a related footgun.",
449
+ "outputSha256": "664a4d6ce336be79663676db1ea2dfdd62862e8044363d0f5308fb4d35f282f1",
450
+ "promptSha256": "c78d52586b35c13972c76fdbe74219b195ecdff3b3ee2d4bafd38d754ba8b894",
451
+ "deterministic": [],
452
+ "judge": {
453
+ "verdict": "pass",
454
+ "reason": "Answer says 'No' and calls direct capture wrong (constructor runs once, captures the DbContext). Fix: inject IServiceScopeFactory and CreateAsyncScope()/CreateScope() to resolve a fresh OrderDbContext per call, shown in code. Consequence: single DbContext reused across every request/thread for the process lifetime, not fresh per operation."
455
+ },
456
+ "passed": true
457
+ }
458
+ ]
459
+ }
460
+ ],
461
+ "verdict": "fail",
462
+ "scope": "bundled",
463
+ "skillDigest": "39976e1d9ee5016ad48f694c27a6a87faa5c24ec03e89279ae641f5f01515cf0",
464
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
465
+ "judgePromptVersion": "2026-09-25.1",
466
+ "runner": "deepseek",
467
+ "model": "deepseek-chat",
468
+ "runnerPromptVersion": "2026-09-25.1",
469
+ "recordedAt": "2026-09-25T18:09:01.715Z",
470
+ "judge": "deepseek",
471
+ "judgeModel": "deepseek-chat"
472
+ },
473
+ {
474
+ "schemaVersion": "1.0.0",
475
+ "skillId": "csharp-dotnet/dotnet-testing",
476
+ "strictness": "high",
477
+ "trials": 10,
478
+ "triggerAccuracy": {
479
+ "truePositive": 4,
480
+ "falsePositive": 0,
481
+ "positives": 7,
482
+ "negatives": 8
483
+ },
484
+ "evidence": "authored",
485
+ "scenarios": [
486
+ {
487
+ "id": "trigger-positive-1",
488
+ "kind": "trigger-positive",
489
+ "prompt": "This validator has zero test coverage right now, can you add some?",
490
+ "strictness": "high",
491
+ "trials": 1,
492
+ "passes": 0,
493
+ "passRate": 0,
494
+ "passAtK": 0,
495
+ "grader": "trigger-rank-fork-family",
496
+ "status": "ran",
497
+ "deterministic": true
498
+ },
499
+ {
500
+ "id": "trigger-positive-2",
501
+ "kind": "trigger-positive",
502
+ "prompt": "I need Moq set up for this class's HTTP dependency",
503
+ "strictness": "high",
504
+ "trials": 1,
505
+ "passes": 1,
506
+ "passRate": 1,
507
+ "passAtK": 1,
508
+ "grader": "trigger-rank-fork-family",
509
+ "status": "ran",
510
+ "deterministic": true
511
+ },
512
+ {
513
+ "id": "trigger-positive-3",
514
+ "kind": "trigger-positive",
515
+ "prompt": "What should this xUnit suite mock and what should it exercise for real?",
516
+ "strictness": "high",
517
+ "trials": 1,
518
+ "passes": 0,
519
+ "passRate": 0,
520
+ "passAtK": 0,
521
+ "grader": "trigger-rank-fork-family",
522
+ "status": "ran",
523
+ "deterministic": true
524
+ },
525
+ {
526
+ "id": "trigger-positive-4",
527
+ "kind": "trigger-positive",
528
+ "prompt": "Add TestCase rows for the different discount tiers in NUnit",
529
+ "strictness": "high",
530
+ "trials": 1,
531
+ "passes": 1,
532
+ "passRate": 1,
533
+ "passAtK": 1,
534
+ "grader": "trigger-rank-fork-family",
535
+ "status": "ran",
536
+ "deterministic": true
537
+ },
538
+ {
539
+ "id": "trigger-positive-5",
540
+ "kind": "trigger-positive",
541
+ "prompt": "This dotnet test keeps failing intermittently, help me stabilize it",
542
+ "strictness": "high",
543
+ "trials": 1,
544
+ "passes": 1,
545
+ "passRate": 1,
546
+ "passAtK": 1,
547
+ "grader": "trigger-rank-fork-family",
548
+ "status": "ran",
549
+ "deterministic": true
550
+ },
551
+ {
552
+ "id": "trigger-positive-6",
553
+ "kind": "trigger-positive",
554
+ "prompt": "None of the async methods on this repository class are tested -- can you take care of that?",
555
+ "strictness": "high",
556
+ "trials": 1,
557
+ "passes": 1,
558
+ "passRate": 1,
559
+ "passAtK": 1,
560
+ "grader": "trigger-rank-fork-family",
561
+ "status": "ran",
562
+ "deterministic": true
563
+ },
564
+ {
565
+ "id": "trigger-positive-7",
566
+ "kind": "trigger-positive",
567
+ "prompt": "Set up NSubstitute for the payment gateway interface in this test",
568
+ "strictness": "high",
569
+ "trials": 1,
570
+ "passes": 0,
571
+ "passRate": 0,
572
+ "passAtK": 0,
573
+ "grader": "trigger-rank-fork-family",
574
+ "status": "ran",
575
+ "deterministic": true
576
+ },
577
+ {
578
+ "id": "trigger-negative-1",
579
+ "kind": "trigger-negative",
580
+ "prompt": "Write table-driven pytest cases for this Python function",
581
+ "strictness": "high",
582
+ "trials": 1,
583
+ "passes": 1,
584
+ "passRate": 1,
585
+ "passAtK": 1,
586
+ "grader": "trigger-rank-fork-family",
587
+ "status": "ran",
588
+ "deterministic": true
589
+ },
590
+ {
591
+ "id": "trigger-negative-2",
592
+ "kind": "trigger-negative",
593
+ "prompt": "Add Jest tests for this React component's rendering",
594
+ "strictness": "high",
595
+ "trials": 1,
596
+ "passes": 1,
597
+ "passRate": 1,
598
+ "passAtK": 1,
599
+ "grader": "trigger-rank-fork-family",
600
+ "status": "ran",
601
+ "deterministic": true
602
+ },
603
+ {
604
+ "id": "trigger-negative-3",
605
+ "kind": "trigger-negative",
606
+ "prompt": "Write Go tests for this package using t.Run subtests",
607
+ "strictness": "high",
608
+ "trials": 1,
609
+ "passes": 1,
610
+ "passRate": 1,
611
+ "passAtK": 1,
612
+ "grader": "trigger-rank-fork-family",
613
+ "status": "ran",
614
+ "deterministic": true
615
+ },
616
+ {
617
+ "id": "trigger-negative-4",
618
+ "kind": "trigger-negative",
619
+ "prompt": "Write XCTest cases for this Swift view model",
620
+ "strictness": "high",
621
+ "trials": 1,
622
+ "passes": 1,
623
+ "passRate": 1,
624
+ "passAtK": 1,
625
+ "grader": "trigger-rank-fork-family",
626
+ "status": "ran",
627
+ "deterministic": true
628
+ },
629
+ {
630
+ "id": "trigger-negative-5",
631
+ "kind": "trigger-negative",
632
+ "prompt": "Add Espresso/JUnit tests for this Kotlin Android activity",
633
+ "strictness": "high",
634
+ "trials": 1,
635
+ "passes": 1,
636
+ "passRate": 1,
637
+ "passAtK": 1,
638
+ "grader": "trigger-rank-fork-family",
639
+ "status": "ran",
640
+ "deterministic": true
641
+ },
642
+ {
643
+ "id": "trigger-negative-6",
644
+ "kind": "trigger-negative",
645
+ "prompt": "Write a Flutter widget test using WidgetTester for this screen",
646
+ "strictness": "high",
647
+ "trials": 1,
648
+ "passes": 1,
649
+ "passRate": 1,
650
+ "passAtK": 1,
651
+ "grader": "trigger-rank-fork-family",
652
+ "status": "ran",
653
+ "deterministic": true
654
+ },
655
+ {
656
+ "id": "trigger-negative-7",
657
+ "kind": "trigger-negative",
658
+ "prompt": "Implement this feature in C# using a Scoped DbContext",
659
+ "strictness": "high",
660
+ "trials": 1,
661
+ "passes": 1,
662
+ "passRate": 1,
663
+ "passAtK": 1,
664
+ "grader": "trigger-rank-fork-family",
665
+ "status": "ran",
666
+ "deterministic": true
667
+ },
668
+ {
669
+ "id": "trigger-negative-8",
670
+ "kind": "trigger-negative",
671
+ "prompt": "Review this C# diff for swallowed exceptions",
672
+ "strictness": "high",
673
+ "trials": 1,
674
+ "passes": 1,
675
+ "passRate": 1,
676
+ "passAtK": 1,
677
+ "grader": "trigger-rank-fork-family",
678
+ "status": "ran",
679
+ "deterministic": true
680
+ },
681
+ {
682
+ "id": "mock-external-not-internal",
683
+ "kind": "behavior",
684
+ "prompt": "I'm writing a unit test for OrderService.PlaceOrder. It calls IPaymentGateway.Charge (an interface wrapping an HTTP call to a payment provider) and also calls its own private method CalculateTotal to work out the amount to charge. What should this test mock?",
685
+ "strictness": "high",
686
+ "trials": 10,
687
+ "passes": 10,
688
+ "passRate": 1,
689
+ "passAtK": 1,
690
+ "grader": "judge",
691
+ "status": "ran",
692
+ "trialRecords": [
693
+ {
694
+ "output": "Mock `IPaymentGateway` only. Do not mock `CalculateTotal`.\n\n**Why:**\n\n- `IPaymentGateway` is a genuine external seam — it wraps an HTTP call to a payment provider. In a unit test you can't (and shouldn't) hit the real provider, so you stub it: `Mock<IPaymentGateway>` (or `Substitute.For<IPaymentGateway>()`), set up `Charge(...)` for the happy path, and optionally `Verify` that it was called with the amount your test expects.\n- `CalculateTotal` is a private method *inside* the unit under test (`OrderService`). Mocking it would prove nothing — you'd only be asserting that a mock returns what you configured it to return, not that the real total calculation works. Call `PlaceOrder` with real inputs and let `CalculateTotal` run for real; then assert on the amount by inspecting what was passed to `IPaymentGateway.Charge` (via a Moq `It.Is<...>`/capture or an NSubstitute `Received()` check).\n\n**A useful consequence:** if you don't mock `CalculateTotal`, you can actually test it through the public `PlaceOrder` surface. Feed in an order (line items, quantities, any discount/tax inputs) whose expected total you've computed by hand, capture the amount passed to `Charge`, and assert it equals that expected value. That's a real test of the calculation logic — mocking `CalculateTotal` would throw that coverage away.\n\n**Shape of the test (xUnit + Moq):**\n\n```csharp\n[Theory]\n[InlineData(2, 10.00, 20.00)]\n[InlineData(1, 5.50, 5.50)]\n[InlineData(3, 0.00, 0.00)] // boundary: free items\npublic async Task PlaceOrder_ChargesCalculatedTotal(int qty, decimal unitPrice, decimal expectedTotal)\n{\n var gateway = new Mock<IPaymentGateway>();\n gateway.Setup(g => g.Charge(It.IsAny<decimal>(), It.IsAny<CancellationToken>()))\n .ReturnsAsync(PaymentResult.Succeeded);\n\n var sut = new OrderService(gateway.Object);\n var order = new Order(new LineItem(qty, unitPrice));\n\n await sut.PlaceOrder(order);\n\n gateway.Verify(g => g.Charge(expectedTotal, It.IsAny<CancellationToken>()), Times.Once);\n}\n```\n\nTwo things to watch:\n\n- Make the test method `async Task`, not `async void`, since `PlaceOrder` is async — otherwise a failed assertion inside it is silently swallowed by the runner.\n- If a test needs to confirm *which* validation/error path fires, assert the specific exception type (and message/properties), not just `Assert.NotNull(result)` — otherwise a regression in *which* error occurs goes unnoticed.",
695
+ "outputSha256": "f1b9200c1146f8e34bd15c17507c5cde1aa1fa054f5bbc3740ba2c9b4fab498c",
696
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
697
+ "deterministic": [],
698
+ "judge": {
699
+ "verdict": "pass",
700
+ "reason": "Answer says mock IPaymentGateway only, naming it as external seam wrapping HTTP, with Mock<IPaymentGateway>/Substitute.For<IPaymentGateway>() (criterion 1). It states CalculateTotal should not be mocked and should run for real, naming it specifically (criterion 2). It gives the reason: gateway crosses network boundary, CalculateTotal is internal logic that is itself what the test verifies (criterion 3). No fail criterion: it never recommends mocking CalculateTotal."
701
+ },
702
+ "passed": true
703
+ },
704
+ {
705
+ "output": "Mock only `IPaymentGateway`. It's the external seam — an HTTP call to a third-party provider, which you can't run in a unit test and shouldn't want to.\n\nDo **not** mock `CalculateTotal`. It's a private method of the class under test (`OrderService` itself). Mocking it isn't even mechanically possible from outside, and trying to restructure the class (extract an interface, make it `internal` + `InternalsVisibleTo`, etc.) just so you can stub it is the wrong move: once `CalculateTotal` is mocked out, your test only proves that `PlaceOrder` charges whatever the mock was told to return, not that the real total calculation feeds the charge correctly.\n\nThe right shape:\n\n```csharp\n[Fact]\npublic async Task PlaceOrder_ValidCart_ChargesSumOfLineItems()\n{\n // Arrange — mock the external seam only\n var gateway = new Mock<IPaymentGateway>();\n decimal chargedAmount = 0;\n gateway\n .Setup(g => g.Charge(It.IsAny<decimal>(), It.IsAny<string>()))\n .Callback<decimal, string>((amount, _) => chargedAmount = amount)\n .ReturnsAsync(new ChargeResult(succeeded: true));\n\n var service = new OrderService(gateway.Object);\n\n var cart = new Cart { Items = { /* line items summing to a known total */ } };\n\n // Act\n var result = await service.PlaceOrder(cart);\n\n // Assert — the *real* CalculateTotal result reached the gateway\n Assert.True(result.Succeeded);\n Assert.Equal(/* the real expected total, computed by hand */ 137.50m, chargedAmount);\n}\n```\n\nWhy this works: by letting the real `CalculateTotal` run and then asserting on the amount handed to the mocked `IPaymentGateway.Charge`, the calculation and the charge are tested *together* through the public entry point. If `CalculateTotal` breaks (off-by-one, wrong discount order, missed tax), the `chargedAmount` assertion fails — which is the signal you actually want. That coupling is the test's whole purpose here; mocking `CalculateTotal` would cut exactly that wire.\n\nA couple of practical follow-ons:\n\n- Since `CalculateTotal` is private, cover it through `PlaceOrder` (or another public method that uses it), not by using reflection to call the private method directly. Reflection-based direct tests are brittle against renames and don't exercise the real call site.\n- If the total depends on a clock, tax table, or pricing service, *those* are the additional external seams worth injecting and stubbing. `CalculateTotal` itself is the unit's own logic and runs for real.\n- Use `mock.Verify(g => g.Charge(expectedAmount, ...), Times.Once)` only if \"charged exactly once\" is genuinely part of the contract you're testing — e.g. a retry/duplicate-charge guard. Otherwise the callback assertion above is the cleaner check, since it verifies *what* was charged rather than just that some charge happened.\n\nBottom line: mock `IPaymentGateway` (HTTP boundary), let `CalculateTotal` execute for real, and assert that the real total arrives at the gateway.",
706
+ "outputSha256": "0a9b9bedb0b620ddd03500e2978e8537a4436773fcd722340a726616f224890f",
707
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
708
+ "deterministic": [],
709
+ "judge": {
710
+ "verdict": "pass",
711
+ "reason": "Answer says mock only IPaymentGateway, naming it the external HTTP seam, and shows Mock<IPaymentGateway> with Setup on Charge. It explicitly says do NOT mock CalculateTotal, letting it run for real, and asserts the real total reaches the gateway. It gives the reason: IPaymentGateway crosses an HTTP/third-party boundary while CalculateTotal is OrderService's own logic that the test must verify. No fail criterion holds."
712
+ },
713
+ "passed": true
714
+ },
715
+ {
716
+ "output": "Mock only `IPaymentGateway.Charge` — it's a genuine external seam (an HTTP call to a third party). Give the test a real `OrderService` instance with a stubbed `IPaymentGateway` so you control what `Charge` returns/throws and can verify it was called with the right amount.\n\nDo **not** mock `CalculateTotal`. It's the unit's own internal logic, and if you mock it out you've only proven that your mock returns what you told it to — you haven't verified the total actually gets computed correctly, which is the whole point of testing `PlaceOrder`. Set up real input (order lines, quantities, prices) that make the correct total deterministic, and assert on what gets passed to `Charge`. If reaching `CalculateTotal` deterministically is genuinely painful, that's a seam problem worth naming in the report — but the fix is refactoring the calculation into something injectable/testable at the source level, not mocking a private helper from the test side.",
717
+ "outputSha256": "646071d5fa5de71d6d6d44d370cded028dab388efc4ed32e91440240790087c1",
718
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
719
+ "deterministic": [],
720
+ "judge": {
721
+ "verdict": "pass",
722
+ "reason": "Answer says mock only IPaymentGateway.Charge, naming it as a genuine external seam (HTTP call to third party) with a stubbed IPaymentGateway. It explicitly says do NOT mock CalculateTotal, calling it the unit's own internal logic, and explains mocking it only proves the mock returns what you configured rather than verifying the total is computed correctly. No fail criterion holds."
723
+ },
724
+ "passed": true
725
+ },
726
+ {
727
+ "output": "Mock `IPaymentGateway` only. It's a genuine external seam — an HTTP call to a third party, non-deterministic, network-dependent, and something you want to control (force a success, a decline, a timeout) without hitting a real provider. So:\n\n```csharp\nvar gateway = new Mock<IPaymentGateway>();\ngateway.Setup(g => g.Charge(It.IsAny<decimal>(), It.IsAny<string>()))\n .ReturnsAsync(new ChargeResult { Succeeded = true });\n```\n\nDo **not** mock `CalculateTotal`. It's a private method of `OrderService` itself — part of the logic you're actually testing. If you could mock it (you can't, cleanly, and reaching for `InternalsVisibleTo`/reflection to make it mockable is a smell), you'd only be proving that your stub returns what you told it to, not that the real total calculation is correct. Instead, drive it through real inputs:\n\n```csharp\n[Fact]\npublic async Task PlaceOrder_ValidOrder_ChargesGatewayWithCalculatedTotal()\n{\n // Arrange: order with items whose real total is a known value\n var order = new Order(new[]\n {\n new OrderLine(unitPrice: 10.00m, quantity: 2), // 20.00\n new OrderLine(unitPrice: 5.50m, quantity: 3), // 16.50 -> total 36.50\n });\n var gateway = new Mock<IPaymentGateway>();\n gateway.Setup(g => g.Charge(It.IsAny<decimal>(), It.IsAny<string>()))\n .ReturnsAsync(new ChargeResult { Succeeded = true });\n var sut = new OrderService(gateway.Object);\n\n // Act\n await sut.PlaceOrder(order);\n\n // Assert: the real CalculateTotal produced 36.50, and it reached the seam\n gateway.Verify(g => g.Charge(36.50m, order.CustomerId), Times.Once);\n}\n```\n\nThat `Verify` on the exact amount is the point: it asserts the interaction at the seam, and because `CalculateTotal` ran for real, the amount is genuine output of the unit under test rather than a value you asserted against your own stub.\n\nTwo things to watch:\n\n- **Mock the interface, not an `HttpClient`.** If `IPaymentGateway` is a clean interface, Moq/NSubstitute it directly. Only fall back to a fake `HttpMessageHandler` if the service under test takes a raw `HttpClient` and there's no seam above it — and even then, that handler is still the external seam, not `CalculateTotal`.\n- **Prefer a `Theory` for the total.** Different line combinations (empty order, quantity 0, rounding, discount threshold) map naturally to `[InlineData]` rows, and each row still asserts the real calculated amount reaching `Charge` — same mocking strategy, more coverage.\n\nShort version: mock the thing that leaves the process (`IPaymentGateway`); run real everything that is `OrderService`'s own reasoning (`CalculateTotal`).",
728
+ "outputSha256": "9ae969065f7ae5a25fa8bd1d3cb4a87a3ffb6220142c823a4f2eda89ee91e422",
729
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
730
+ "deterministic": [],
731
+ "judge": {
732
+ "verdict": "pass",
733
+ "reason": "Answer names IPaymentGateway as the external seam to mock (Mock<IPaymentGateway> with Setup/Verify), explicitly says do NOT mock CalculateTotal and to run it for real, and gives the reason: gateway crosses network/external boundary while CalculateTotal is OrderService's own logic being verified (Verify on exact 36.50 proves real calculation). No fail criterion met."
734
+ },
735
+ "passed": true
736
+ },
737
+ {
738
+ "output": "Mock `IPaymentGateway` only. Give the mock a deterministic `Charge` result (success with a charge id, or a declined result, depending on the case), then assert on `OrderService`'s real output: the order state/result it returns and, if the amount charged is part of the contract, `mock.Verify(g => g.Charge(orderId, 123.45m), Times.Once)` — or capture the argument with a callback and assert on it.\n\nDo not mock or try to intercept `CalculateTotal`. It's a private method on the class under test, so it isn't a seam — there's nothing to substitute, and even if you extracted it behind an interface just to mock it, you'd be testing that your mock returns the value you told it to return, not that the total is calculated correctly. Let the real calculation run.\n\nConcretely, that splits the coverage like this:\n\n- **`CalculateTotal` correctness** — exercised through `PlaceOrder` by choosing order fixtures whose expected total you compute by hand (e.g. line items + tax + shipping = 123.45m), including edge cases: empty order, zero-quantity line, a discount that reduces the total, rounding at the cent boundary. `[Theory]` + `[InlineData]` is a good fit. If the calculation is complex enough that you want isolated cases, that's a signal to make `CalculateTotal` internal and add `[InternalsVisibleTo]`, or move it to a small collaborator that is genuinely that collaborator's job — but that's a production-side refactor, not something to fake with a mock.\n- **Gateway interaction** — mocked: it's the external HTTP seam. Cover the success path, a declined/failed charge (assert the specific exception or result type, not just non-null), and optionally that a declined charge leaves the order un-confirmed.\n- **Wiring between the two** — the interesting assertion is that the real `CalculateTotal` feeds the amount passed to `Charge`, which is exactly what the `Verify`/callback above checks.\n\nOne boundary to watch: \"mocking an internal collaborator\" also means don't mock a `TaxCalculator`/`ShippingCalculator` the service owns and calls synchronously just to make the amount come out to a round number — that would skip the calculation you're trying to verify. Mock only things that leave the process: the payment HTTP call, a clock, a repository, a message publisher.",
739
+ "outputSha256": "d5e53ea4e1fe788397fb4eeccb5d3ab23cdb61749e63ddc44210e165910a8899",
740
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
741
+ "deterministic": [],
742
+ "judge": {
743
+ "verdict": "pass",
744
+ "reason": "Answer says mock IPaymentGateway only, naming it the external HTTP seam (\"it's the external HTTP seam\"). It explicitly says do not mock/intercept CalculateTotal, letting the real calculation run, and explains the distinction: gateway crosses a network boundary while CalculateTotal is internal logic whose correctness the test must verify (\"you'd be testing that your mock returns the value you told it to return\"). No fail criterion met."
745
+ },
746
+ "passed": true
747
+ },
748
+ {
749
+ "output": "Mock `IPaymentGateway` — it wraps a real external HTTP call to a payment provider, so it's a genuine external seam you should stub out. Everything about `CalculateTotal` — the real algorithm, its edge cases, its arithmetic — should run for real inside `PlaceOrder`; you set up input values that flow through to a real computed total, then assert the amount that `PlaceOrder` actually passed to `IPaymentGateway.Charge`.\n\nConcretely:\n\n- Construct `Mock<IPaymentGateway>` (Moq) or `Substitute.For<IPaymentGateway>()` (NSubstitute), set up `Charge` to return a success result, and inject it into `OrderService` through its constructor.\n- Do NOT extract-and-mock `CalculateTotal`, and do not make it `virtual`/`internal` just so a mock can intercept it. It's an internal collaborator of the unit under test (OrderService.PlaceOrder), and mocking it would only prove your mock returns what you told it to — it would prove nothing about whether the real total calculation is correct.\n- To assert the charged amount, use the mock as an observation point: `mock.Verify(g => g.Charge(It.Is<PaymentRequest>(r => r.Amount == 42.50m)), Times.Once)`, or capture the argument with a callback if you want to assert on more of its shape. That way the real `CalculateTotal` runs, and the mock only records what it was asked to charge.\n- Separately, exercise `CalculateTotal`'s own edge cases (empty cart, zero quantity, rounding, discounts) through `PlaceOrder` inputs or a dedicated test of the total calculation — not by stubbing the method out.\n\nOne caveat worth flagging: if `CalculateTotal` is `private`, that's a design smell for testability but not a reason to mock it. Test it indirectly through the public `PlaceOrder` surface; only if you later need focused unit tests on the math itself would you consider making it `internal` + `InternalsVisibleTo` (or moving the math into a small injectable pure helper) — a production-code change to call out explicitly, not a silent mock target.",
750
+ "outputSha256": "66b95cfa242eead1513b15aca9437dd14e72ae38bf89e6c592995e4a0a8f5731",
751
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
752
+ "deterministic": [],
753
+ "judge": {
754
+ "verdict": "pass",
755
+ "reason": "Answer names IPaymentGateway as the external HTTP seam to mock via Mock<IPaymentGateway>/Substitute.For<IPaymentGateway>() (criterion 1). It explicitly says CalculateTotal should run for real, not be mocked/extracted/virtualized (criterion 2). It gives the reason: IPaymentGateway wraps a real external HTTP call while CalculateTotal is internal logic whose correctness the test must verify (criterion 3). No fail criterion: it warns against mocking CalculateTotal."
756
+ },
757
+ "passed": true
758
+ },
759
+ {
760
+ "output": "For this test, mock only `IPaymentGateway` — it's the external seam (an interface wrapping an HTTP call to a real payment provider), so substituting it lets the test control what \"the payment provider\" returns without ever making a network call.\n\n`CalculateTotal` should **not** be mocked, and in fact can't be in the normal sense — it's a private method on `OrderService`, the unit under test. Mocking out the class's own internal logic would mean the test only proves that your stub returned what you told it to, not that `CalculateTotal` actually computes the right amount to charge. Exercise it for real by passing real inputs (line items, quantities, discounts, whatever feeds it) and asserting that `IPaymentGateway.Charge` was called with the amount `CalculateTotal` should actually produce.\n\nIf `CalculateTotal` is hard to drive through the real `PlaceOrder` path — buried behind other setup you don't care about for this test — that's a design signal, not a mocking signal: extract it into a small collaborator (e.g. an `IOrderTotalCalculator`) and inject it, then your test can control its output via a real implementation or a simple fake. But you don't extract it in order to mock it in this test; you extract it because the seam is genuinely useful for the production design, and mocking becomes an option afterward.\n\nSo concretely: construct a `Mock<IPaymentGateway>` (or `Substitute.For<IPaymentGateway>()`), set up its `Charge` return/behavior, call the real `OrderService.PlaceOrder(...)` with real inputs, then assert on the resulting state and — if the charge *occurrence* is part of the contract — `Verify` that `Charge` was called once with the expected amount. The exact amount assertion is where your real `CalculateTotal` coverage comes from; without it the test wouldn't catch a pricing regression even with the gateway mock set up correctly.",
761
+ "outputSha256": "21f4efd438793d546e1664de4a9237de8b368b9e14f8c351477555096302fbe7",
762
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
763
+ "deterministic": [],
764
+ "judge": {
765
+ "verdict": "pass",
766
+ "reason": "Answer names IPaymentGateway as the external seam to mock via Mock<IPaymentGateway>/Substitute.For<IPaymentGateway>() (para 1, final para). It explicitly says CalculateTotal should NOT be mocked and must run for real, asserting Charge was called with the amount CalculateTotal produces. It gives the reason: gateway crosses a network boundary while CalculateTotal is internal logic the test must verify. No fail criterion holds."
767
+ },
768
+ "passed": true
769
+ },
770
+ {
771
+ "output": "Mock `IPaymentGateway` only. It's the genuine external seam — an HTTP call to a third-party provider you don't control, whose real behavior (network, latency, auth, response codes) you cannot and should not exercise in a unit test.\n\nDo **not** mock `CalculateTotal`. It's a private method of the unit under test (`OrderService`), so it's part of the logic you're verifying, not a boundary. Since it's private you can't mock it directly anyway — and you shouldn't want to: a test that stubs out the very calculation that determines the charge amount proves only that your stub returns what you told it to, not that `PlaceOrder` computes the amount correctly. Exercise it for real by constructing `OrderService` with real inputs (e.g. an order with known line items) and asserting the amount passed to the gateway.\n\nThat last part is worth stating explicitly: to verify `CalculateTotal` participated correctly, assert **on the value handed to `Charge`**, not just that `Charge` was called. In Moq that's a callback or an argument matcher:\n\n```csharp\nvar gateway = new Mock<IPaymentGateway>();\ngateway\n .Setup(g => g.Charge(It.IsAny<decimal>(), It.IsAny<string>()))\n .ReturnsAsync(new ChargeResult { Succeeded = true });\n\nvar service = new OrderService(gateway.Object, /* real deps */);\nvar result = await service.PlaceOrder(orderWithTwoItems);\n\ngateway.Verify(g => g.Charge(42.00m, \"usd\"), Times.Once);\nAssert.True(result.Succeeded);\n```\n\nTwo notes:\n\n- If `OrderService` takes its other dependencies (clock, repository, etc.) as interfaces, those are seamed the same way — mock them too if you need deterministic time or persistence in the test. But mock them because they're external boundaries, not because mocking is the default.\n- If you find yourself wanting to mock `CalculateTotal` to make a case \"easier,\" that's the signal in the Red Flags table: make it a real, non-primitive input instead, or pull it into its own testable method and test it directly rather than mocking it out of `PlaceOrder`.\n\nIf the amount assertion is awkward to express for many cases, that's a good reason to add a separate `[Theory]`/`[InlineData]` table directly against `CalculateTotal` (made `internal` with `InternalsVisibleTo`, or refactored into a small calculator class) rather than reaching for a mock.",
772
+ "outputSha256": "002ce8d5ce1830d3d2097ad8b0850c7b0aa8a8f2d39e2dbf43b5dfd2b86abe2f",
773
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
774
+ "deterministic": [],
775
+ "judge": {
776
+ "verdict": "pass",
777
+ "reason": "Answer says mock IPaymentGateway only, naming it the external seam (HTTP call to third-party provider). It explicitly says do NOT mock CalculateTotal, calling it part of the logic being verified, and shows exercising it for real with real inputs and asserting the value passed to Charge. Reason given: gateway crosses network/external boundary; CalculateTotal is internal logic the test must verify. No fail criterion met."
778
+ },
779
+ "passed": true
780
+ },
781
+ {
782
+ "output": "Mock `IPaymentGateway` — that's the external seam (an interface wrapping an outbound HTTP call to a third party). Control its behavior with a setup (`mock.Setup(g => g.Charge(...))` in Moq / `Substitute.For<IPaymentGateway>()` in NSubstitute) so `PlaceOrder` runs deterministically without a real network call, and verify against it only if the charge call itself is part of the contract you're asserting (e.g. that the correct amount was passed).\n\nDo **not** mock `CalculateTotal`. It's a private method on `OrderService` itself — part of the unit under test. You can't mock it directly anyway (private methods aren't virtual/overridable, and `OrderService` isn't an injected dependency here), and even if you could refactor it to be mockable, doing so would defeat the test: it would prove only that your stub returns what you told it to, not that the real total calculation is correct. Instead, construct inputs such that the expected total is computable by hand, then assert that `IPaymentGateway.Charge` was called with that value — exercising the real `CalculateTotal` in the process.\n\nSo the shape is: real `OrderService`, fake `IPaymentGateway`, real total logic, and an assertion (or mock verify) that the amount handed to the gateway matches the total you computed independently from the inputs.",
783
+ "outputSha256": "2057d0e24c2950b97eb1fca674eef97c015489d6c77219ff26c72ad44a5f444f",
784
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
785
+ "deterministic": [],
786
+ "judge": {
787
+ "verdict": "pass",
788
+ "reason": "Answer names IPaymentGateway as the external seam to mock (Moq Setup / Substitute.For<IPaymentGateway>), explicitly says do NOT mock CalculateTotal and to exercise the real calculation, and gives the reason: gateway crosses a network boundary while CalculateTotal is internal logic whose correctness is what the test verifies. No fail criterion triggered."
789
+ },
790
+ "passed": true
791
+ },
792
+ {
793
+ "output": "Mock `IPaymentGateway` (the external seam), and exercise `CalculateTotal` for real.\n\nHere's the split and why:\n\n**Mock `IPaymentGateway`**\n- It's an interface wrapping an HTTP call to a third-party payment provider — a classic external seam. Hitting the real thing in a unit test means network flakiness, real (or sandbox) charges, and nondeterministic latency.\n- Configure it with Moq (`var gateway = new Mock<IPaymentGateway>()`) or NSubstitute (`Substitute.For<IPaymentGateway>()`), matching whatever the project already uses. When `PlaceOrder` calls `Charge`, your stub just returns a canned result.\n- You can also *verify* the interaction if the amount is part of the contract: `gateway.Verify(g => g.Charge(expectedAmount), Times.Once)` — that's a legitimate use of `Verify` here because \"charged the right amount exactly once\" is part of what you're testing.\n\n**Do NOT mock `CalculateTotal`**\n- It's a private method of the class under test. It's not a collaborator — it *is* the logic under test. Mocking it would prove only that your mock returns the number you told it to return, not that the total is actually computed correctly.\n- Because it's private, you also can't mock it directly anyway (Moq/NSubstitute mock interfaces and overridable virtual members, not private methods). The temptation to reach for reflection or make it `public virtual` just to intercept it is the anti-pattern to avoid.\n- Instead, drive it through real inputs: call `PlaceOrder` with a real order whose line items you control, and assert on the amount that reaches `IPaymentGateway.Charge`. That way a bug in the total math shows up as a failing test.\n\n**Why this matters for this specific test**\nSince `CalculateTotal`'s output feeds directly into `Charge`, the most valuable assertion is the boundary between them: *\"given these line items, `PlaceOrder` calls `Charge` with this exact amount.\"* That test fails if the total math regresses *or* if the wiring regresses — which is exactly what you want. If you mocked `CalculateTotal`, neither could fail.\n\nSo the shape is roughly:\n\n```csharp\n[Fact]\npublic async Task PlaceOrder_WithValidItems_ChargesCalculatedTotal()\n{\n var gateway = new Mock<IPaymentGateway>();\n var service = new OrderService(gateway.Object);\n var order = new Order { Items = { /* known items summing to 42.00m */ } };\n\n await service.PlaceOrder(order);\n\n gateway.Verify(g => g.Charge(42.00m), Times.Once);\n}\n```\n\nIf the total math has enough edge cases to justify it (discounts, tax, rounding, empty cart), cover it the same way — a `[Theory]`/`[InlineData]` table of input line items → expected `Charge` amount — rather than trying to test `CalculateTotal` in isolation through a mock.\n\nOne caveat: if `CalculateTotal` ends up being genuinely complex and you find yourself writing many order-shape variations just to test it, that's a signal it deserves to be its own testable unit (extracted to a method the test can call directly, or a separate calculator class) — but that's a refactor decision, not a reason to mock it now.",
794
+ "outputSha256": "b0a9b179a4bbd60e7acbd777b5ff56d80a4fca2227cc5ba387906f613cd67f98",
795
+ "promptSha256": "4f0f952a5138aa125e2bfd472e812de84699b46a8b12adb4c215ed963a8652b0",
796
+ "deterministic": [],
797
+ "judge": {
798
+ "verdict": "pass",
799
+ "reason": "Answer names IPaymentGateway as the external seam to mock, showing Mock<IPaymentGateway> and Substitute.For<IPaymentGateway>() (criterion 1). It explicitly says do NOT mock CalculateTotal, which should run for real via real inputs (criterion 2). It gives the reason: IPaymentGateway wraps an HTTP/network boundary while CalculateTotal is internal logic that is itself under test (criterion 3). No fail criterion holds."
800
+ },
801
+ "passed": true
802
+ }
803
+ ]
804
+ },
805
+ {
806
+ "id": "no-sleep-async-wait",
807
+ "kind": "behavior",
808
+ "prompt": "My xUnit test starts a background operation via an IHostedService and I want to wait for it to actually finish before running my assertions. What's the right way to do that in C#?",
809
+ "strictness": "high",
810
+ "trials": 10,
811
+ "passes": 10,
812
+ "passRate": 1,
813
+ "passAtK": 1,
814
+ "grader": "judge",
815
+ "status": "ran",
816
+ "trialRecords": [
817
+ {
818
+ "output": "The right way is to await a real completion signal from the background operation — never `Thread.Sleep`/`Task.Delay` as a synchronization hack, and never `.Result`/`.Wait()` in a test method.\n\nThe actual mechanism depends on how the `IHostedService` exposes completion. The three patterns that work, in order of preference:\n\n**1. The service exposes a completion `Task` (best).**\n\nIf you're writing or can extend the hosted service, have `StartAsync` kick off the work and store the `Task` on a public property:\n\n```csharp\npublic class OrderProcessor : BackgroundService\n{\n public Task ProcessingTask => _processingTask; // set in ExecuteAsync\n}\n\n// test\nawait _host.Services.GetRequiredService<OrderProcessor>().ProcessingTask;\n```\n\nThis is deterministic: you're awaiting the same `Task` the runtime awaits, so there's no race and no arbitrary timeout.\n\n**2. Signal via `TaskCompletionSource` from a seam you control.**\n\nIf the background loop calls an injected dependency (a repository, a queue client) as its last meaningful step, have the test's fake/substitute complete a `TaskCompletionSource` on that call:\n\n```csharp\nvar tcs = new TaskCompletionSource();\nvar repo = new Mock<IOrderRepository>();\nrepo.Setup(r => r.SaveAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))\n .Callback(() => tcs.TrySetResult())\n .Returns(Task.CompletedTask);\n\n// ... start host ...\nawait tcs.Task; // completes the instant the work is really done\n```\n\nAdd a timeout with `await tcs.Task.WaitAsync(TimeSpan.FromSeconds(5))` so a genuine hang fails the test instead of hanging the run.\n\n**3. Synchronization primitive the service already uses.**\n\nIf the service signals through an existing `SemaphoreSlim`, `Channel`, or similar, await that directly (`await semaphore.WaitAsync(timeout)`). Don't invent a second signaling mechanism alongside one that's already there.\n\n**Use `WebApplicationFactory` correctly if this is an integration test.** With `WebApplicationFactory<TEntryPoint>`, hosted services start when the host builds. Drive it with `WithWebHostBuilder`, create the client, then await your completion signal. `IHost.StopAsync()`/disposal will also await the background work, but only at teardown — that's too late for mid-test assertions, so it's not a substitute for a real signal.\n\n**What to avoid, and why:**\n\n- `await Task.Delay(2000)` — flaky under CI load; either too short (fails spuriously) or too long (slow suite), and it never actually proves the work finished.\n- `Thread.Sleep` — blocks the test thread and is worse than the above for the same reasons.\n- `.Result`/`.Wait()` on the completion `Task` — risks deadlock and violates the async-test rule; the test method should be `async Task` and `await` directly.\n- Polling a boolean flag in a loop with `Task.Delay` between checks — a disguised sleep; if the service only exposes a flag, prefer adding a `Task`/`TaskCompletionSource` seam instead.\n\n**If the service gives you nothing to await** (no completion task, no injectable seam, no signal), that's the actual problem: either extend the service to expose completion, or inject a dependency whose call marks done. Don't paper over the missing seam with a timeout-and-hope delay — it will fail intermittently and the failure won't tell you why.\n\nWhichever you pick, guard it with `WaitAsync(TimeSpan)` so a real regression surfaces as a fast test failure rather than a hung test run.",
819
+ "outputSha256": "1d5e78b32a7610c4684d1810d7e19471cd412468613b5d8db4dadf8e6e1b726c",
820
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
821
+ "deterministic": [],
822
+ "judge": {
823
+ "verdict": "pass",
824
+ "reason": "Answer names concrete join points: awaiting the service's own Task (`await ...ProcessingTask`), a TaskCompletionSource completed in a mock callback (`await tcs.Task`), and awaiting an existing SemaphoreSlim/Channel. It explains reliability: deterministic, no race, no arbitrary timeout, unlike Task.Delay which is flaky and never proves completion. Thread.Sleep/Task.Delay only warned against."
825
+ },
826
+ "passed": true
827
+ },
828
+ {
829
+ "output": "Wait on a real signal the background operation itself produces, not on elapsed time. `Thread.Sleep`/`Task.Delay(N)` \"to let it finish\" is exactly the flaky pattern to avoid — under load the operation may not be done yet, and a fixed delay either hides that (test passes, assertion races) or makes the suite needlessly slow.\n\nPick the shape based on how the work is exposed:\n\n**If the operation returns/owns a `Task`:** expose it (e.g. the hosted service stores `Task? ExecutionTask` set in `StartAsync`/during `ExecuteAsync`) and `await service.ExecutionTask` in the test, then assert. This is the cleanest case — reach for it first.\n\n**If there's no task handle but there's a completion side effect:** expose a test-visible completion signal from the service and await it:\n\n```csharp\n// In the service (internally visible to the test project via InternalsVisibleTo)\npublic Task Completion => _completion.Task;\nprivate readonly TaskCompletionSource _completion = new(TaskCreationOptions.RunContinuationsAsynchronously);\n\nprotected override async Task ExecuteAsync(CancellationToken stoppingToken)\n{\n try\n {\n await DoBackgroundWorkAsync(stoppingToken);\n _completion.TrySetResult();\n }\n catch (Exception ex)\n {\n _completion.TrySetException(ex);\n throw;\n }\n}\n```\n\nThen in the test:\n\n```csharp\nvar service = new MyHostedService(...);\nvar token = new CancellationTokenSource();\nawait service.StartAsync(token.Token);\n\nawait service.Completion.WaitAsync(TimeSpan.FromSeconds(5)); // bound the wait so a hang fails instead of blocking forever\n```\n\n**If the hosted service is started by the host itself** (`WebApplicationFactory` or a real `IHost`): get the running service instance back out of the container and await its completion signal the same way — don't poll the host.\n\n```csharp\nvar service = _factory.Services.GetRequiredService<MyHostedService>();\nawait service.Completion.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\nA bounded wait via `Task.WaitAsync(timeout)` is deliberate: an unbounded await turns \"the background work never completes\" into a hung test run instead of a clear failure, and a bare delay would instead turn \"the background work silently died\" into a confusing assertion failure.\n\nDo **not**:\n- `Thread.Sleep`/`Task.Delay` as the synchronization mechanism.\n- Poll a lock/flag in a `while` loop with sleeps.\n- `StopAsync` as a stand-in for \"wait for the current work\" unless the service is actually written to signal completion there (most hosted services only observe the stopping token; stopping doesn't imply the in-progress iteration finished).\n\nOne caveat worth checking: if the service must run continuously rather than complete once, waiting on a single completion task is the wrong model — have it signal after the first iteration the test cares about (a `TaskCompletionSource` set once), or inject a mock of whatever external seam (HTTP/repository/clock) it calls and wait on *that* mock's invocation via its own async completion, rather than waiting for the whole hosted service to stop.\n\nIn-framework equivalent if you'd rather not expose a member: xUnit has no built-in \"await background service\" primitive, but the same `TaskCompletionSource` pattern works via a constructor-injected signal (e.g. an `IProgress<T>`/completion interface your service takes), which also keeps the production type free of test-only members.",
830
+ "outputSha256": "e19ae52ca3efdf833300ddfb5a6be47cec61949e6fba6a6a19c74f5062a3e928",
831
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
832
+ "deterministic": [],
833
+ "judge": {
834
+ "verdict": "pass",
835
+ "reason": "Answer names concrete join points: awaiting service.ExecutionTask, and a TaskCompletionSource exposed as service.Completion with _completion.TrySetResult() in ExecuteAsync, awaited via service.Completion.WaitAsync(...). It explains reliability: fixed delay 'under load the operation may not be done yet' and 'either hides that... or makes the suite needlessly slow.' Thread.Sleep/Task.Delay only warned against."
836
+ },
837
+ "passed": true
838
+ },
839
+ {
840
+ "output": "The right approach depends on what signal the background operation can actually give you, but the one thing to avoid in all cases is sleeping and hoping. `Thread.Sleep`/`Task.Delay(N)` before your assertions is non-deterministic and will be flaky under load or in CI — it either wastes time when the operation finishes early or fails spuriously when it finishes late.\n\n**Don't block on `.Result` either.** In an xUnit test that starts the host and waits for a hosted service, blocking the test thread can deadlock depending on synchronization context, and it defeats the point of an async test. Make the test method `async Task` and `await` a real completion signal.\n\nPick the mechanism based on what the service can expose:\n\n**1. Prefer a real completion signal the test can await.** If you control the hosted service (or its production-consumer interface), expose the operation as an awaitable — the standard pattern is a `TaskCompletionSource`:\n\n```csharp\npublic sealed class MyHostedService : BackgroundService\n{\n private readonly TaskCompletionSource _completed =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n public Task Completion => _completed.Task;\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n try\n {\n // ... do the background work ...\n }\n finally\n {\n _completed.TrySetResult();\n }\n }\n}\n```\n\nTest:\n\n```csharp\n[Fact]\npublic async Task HostedService_CompletesWork_BeforeAssertions()\n{\n await using var host = await StartHostAsync();\n var service = host.Services.GetRequiredService<MyHostedService>();\n\n await service.Completion; // deterministic wait, no sleep\n\n // assertions now safe\n}\n```\n\nAdd a bounded timeout so a genuine hang fails the test instead of hanging CI forever — `await service.Completion.WaitAsync(TimeSpan.FromSeconds(10))` (that `WaitAsync` is the BCL one in .NET 6+, not the extension method). A timeout failure is real information; a fixed delay is not.\n\n**2. If you use `IHost`, dispose it after starting.** `await host.StartAsync()` then, at the end of the test, `await host.StopAsync()` (or `await using`). Stopping the host signals the background service's `stoppingToken` and, for the `BackgroundService` base class, awaits its execution task — so the host itself completes the work if the service respects the token. Order matters: stop the host, *then* assert.\n\n**3. If the operation isn't directly awaitable, wait on the observable effect.** E.g. poll a channel/queue/repository with a real bounded retry loop that awaits a check plus a small `Task.Delay` *between attempts* — that's a retry, not a blind sleep, and it fails loudly on timeout:\n\n```csharp\nusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\nwhile (!await repo.HasProcessedAsync(id, cts.Token))\n await Task.Delay(20, cts.Token);\n```\n\nThis is the fallback when no completion signal exists; option 1 or 2 is preferable because it's event-driven rather than polled.\n\n**What to avoid:** `Thread.Sleep`, a bare `await Task.Delay(200)` \"so it finishes,\" `async void` test methods (a failed assertion inside them is silently swallowed — the runner can't await them), `.Result`/`.Wait()`, and any unbounded wait with no timeout.\n\nIn short: make the background operation hand you an awaitable (`TaskCompletionSource`/its own `Task`), `await` it with a bounded timeout, and prefer letting `IHost` disposal/`StopAsync` synchronize via `stoppingToken` when the service is structured that way.",
841
+ "outputSha256": "5e95c80c99e7cc9a224db94e63146460fb953a9229f931478c88605866f5c2a9",
842
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
843
+ "deterministic": [],
844
+ "judge": {
845
+ "verdict": "pass",
846
+ "reason": "Answer names concrete join points: `await service.Completion` backed by a `TaskCompletionSource` (`_completed.TrySetResult()` in finally), plus `await host.StopAsync()`/`await using` awaiting the BackgroundService task. It explains fixed delays are non-deterministic/flaky under load vs. a real signal. Thread.Sleep/Task.Delay only warned against (retry loop is between-attempts, not the wait)."
847
+ },
848
+ "passed": true
849
+ },
850
+ {
851
+ "output": "The right way is to have something concrete and awaitable to await — not to sleep and hope. The specific pattern depends on what the hosted service gives you to observe.\n\n**If the hosted service itself exposes a completion signal**, use it directly. That's the cleanest case:\n\n```csharp\n[Fact]\npublic async Task BackgroundWorker_CompletesProcessing()\n{\n var service = new OrderProcessorHostedService(...);\n\n await service.StartAsync(CancellationToken.None);\n await service.ExecutionTask; // an awaitable the service exposes internally\n\n Assert.Equal(3, repository.ProcessedCount);\n}\n```\n\nThe key move is making the service surface an internal `Task ExecutionTask` (or equivalent) that resolves when `ExecuteAsync` returns. Since `BackgroundService.ExecuteAsync` is what your service overrides, capture that task and expose it. That is the real thing you want to await, and it's deterministic.\n\n**If you only have an interface and can't change the service**, drive it through `IHost` in a test host and use its lifetime to know when it's done:\n\n```csharp\nusing var host = await new HostBuilder()\n .ConfigureServices(services => services.AddHostedService<OrderProcessorHostedService>())\n .StartAsync();\n\n// ... trigger work ...\n\nawait host.StopAsync(); // StartAsync/StopAsync complete the service's lifecycle\n```\n\n`IHost.StartAsync` waits for all `StartAsync` of hosted services to complete, and `StopAsync` waits for them to shut down. If your service does its work in `StartAsync` rather than `ExecuteAsync`, this alone is your synchronization point.\n\n**For a fire-and-forget background loop inside `ExecuteAsync`,** don't poll. Inject a real signal you control — a `TaskCompletionSource`, a `SemaphoreSlim`, or the actual queue's drain signal — and await that.\n\n```csharp\nvar processed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);\n// service calls processed.SetResult() when the unit of work is done\nawait processed.Task.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\nThe `WaitAsync` timeout is the one place a time bound belongs: not as your synchronization mechanism, but as a safety net so a genuinely stuck service fails the test instead of hanging it forever.\n\n**What not to do:**\n\n- `Thread.Sleep` / `Task.Delay` before asserting — flaky under load, and passes for the wrong reason on a fast machine.\n- Awaiting `Task.Delay` and then polling a flag in a loop — same non-determinism with extra steps.\n- Making the test method `async void` — the runner can't await it, so a failure inside is silently dropped.\n\nSo: prefer exposing and awaiting the service's own completion `Task`; if you can't touch the service, drive it through `IHost.StartAsync`/`StopAsync` or inject a `TaskCompletionSource` you await with a timeout. Never sleep.",
852
+ "outputSha256": "83a3d554ac4025a81a108e053424c3ca10eefb882c9ddd7459eb9d3595335218",
853
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
854
+ "deterministic": [],
855
+ "judge": {
856
+ "verdict": "pass",
857
+ "reason": "Answer concretely names real join points: `await service.ExecutionTask` (the service's own Task), `await host.StopAsync()`, and `await processed.Task.WaitAsync(...)` on a TaskCompletionSource. It explains reliability: 'not to sleep and hope', 'deterministic', and warns Thread.Sleep/Task.Delay are 'flaky under load' and 'passes for the wrong reason'. No fail criterion holds."
858
+ },
859
+ "passed": true
860
+ },
861
+ {
862
+ "output": "The right way is to have the hosted service expose a real completion signal you can await — never a `Task.Delay`/`Thread.Sleep` guess. `IHostedService` itself gives you an awkward shape (`StartAsync` is expected to *return* once startup is done, not once the background work finishes), so the signal usually has to be added deliberately.\n\n## Option 1 — inject a signal the test can await (most common fix)\n\nGive the service a `TaskCompletionSource` (or a `Task` it hands out) that is completed when the background loop actually finishes:\n\n```csharp\npublic sealed class OrderWorker : BackgroundService\n{\n private readonly TaskCompletionSource _stopped =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n // Test-facing completion signal: resolves when the background work is done.\n public Task Completion => _stopped.Task;\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n try\n {\n while (!stoppingToken.IsCancellationRequested)\n {\n // ... do the work ...\n }\n }\n finally\n {\n _stopped.TrySetResult();\n }\n }\n}\n```\n\nIn the test, instantiate the service directly, start it, then await the signal:\n\n```csharp\n[Fact]\npublic async Task Worker_ProcessesQueue_WhenStarted()\n{\n var worker = new OrderWorker(/* fakes for repository, etc. */);\n\n await worker.StartAsync(CancellationToken.None);\n // ... enqueue/arrange whatever the background loop consumes ...\n\n await worker.Completion.WaitAsync(TimeSpan.FromSeconds(5)); // await the real task\n\n // now assert on the effects\n}\n```\n\n`WaitAsync(timeout)` (available since .NET 6) is the key detail: it awaits the *real* task but still fails the test instead of hanging forever if the background work never completes.\n\nNote `TaskCreationOptions.RunContinuationsAsynchronously`: without it, completing the TCS from a background thread can synchronously run the test's continuation on that thread, which makes deadlocks and weird timing more likely.\n\n## Option 2 — you're going through the host (`WebApplicationFactory`, `IHost`)\n\nIf the test spins up a real host, resolve the worker and await its signal the same way:\n\n```csharp\npublic class OrderWorkerTests : IClassFixture<WebApplicationFactory<Program>>\n{\n private readonly WebApplicationFactory<Program> _factory;\n\n public OrderWorkerTests(WebApplicationFactory<Program> factory) => _factory = factory;\n\n [Fact]\n public async Task Worker_ProcessesQueue()\n {\n var worker = _factory.Services.GetRequiredService<OrderWorker>();\n\n await worker.StartAsync(CancellationToken.None);\n await worker.Completion.WaitAsync(TimeSpan.FromSeconds(5));\n\n // assert\n }\n}\n```\n\nDon't rely on `IHost.StopAsync` to mean \"work finished\" — for a typical `BackgroundService` that only meaningfully cancels the loop and leaves actual completion to whatever the loop does on the way out, so the explicit signal is still the thing to await.\n\n## If there's no per-operation signal, use a seam\n\nWhen the background work's only observable effect is a call to something like a queue consumer or an external service, express \"it finished\" as a fake you control and await that. With NSubstitute:\n\n```csharp\nvar processed = new TaskCompletionSource();\nconsumer\n .When(c => c.Handle(Arg.Any<Order>()))\n .Do(_ => processed.TrySetResult());\n\n// ...start the worker...\n\nawait processed.Task.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\nMoq equivalent: a `Callback` that sets the same TCS.\n\n## What not to do\n\n- `Thread.Sleep(500)` / `await Task.Delay(500)` before asserting — flaky under load and slow when it passes. This is the exact anti-pattern the checklist calls out.\n- `async void` test methods — the runner can't await them, so failures inside are swallowed.\n- `.Result` / `.Wait()` on the background task — blocks a thread and risks deadlock.\n\n## Getting the signal in place\n\nThe one thing that sometimes needs a production tweak is *having a completion signal at all*. Adding a `Task Completion { get; }` that resolves when the loop exits is a minimal, intent-revealing change and is the cleanest fix — much better than adding an arbitrary delay. If the API genuinely can't expose one, fall back to a fake's callback (the seam approach above) rather than a sleep.\n\nIf you'd like, tell me the service's shape (plain `IHostedService`, `BackgroundService`, or hosted via `WebApplicationFactory`) and what the background work actually touches, and I can write the test with your real types and the right mocking setup.",
863
+ "outputSha256": "742e1461fe17cddc30c3090472cd01d082eb9e022888bc31a3b28cebeeb60ba4",
864
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
865
+ "deterministic": [],
866
+ "judge": {
867
+ "verdict": "pass",
868
+ "reason": "Answer concretely shows a TaskCompletionSource (`_stopped`) exposed as `Completion` and awaited via `await worker.Completion.WaitAsync(...)`, plus a fake-callback TCS seam. It explains the delay is flaky/slow and depends on guessing timing, and explicitly warns against Thread.Sleep/Task.Delay rather than recommending them."
869
+ },
870
+ "passed": true
871
+ },
872
+ {
873
+ "output": "The right way is to give the `IHostedService` (or a collaborator it owns) something awaitable that signals completion, and to `await` that in the test — never a timer. Below is the pattern, plus the anti-pattern it replaces.\n\n## Why timers are wrong here\n\n`Thread.Sleep`/`Task.Delay(200)` makes the test pass or fail based on machine load and timing luck. Under CI load the operation may not have finished; on a fast box you're just wasting wall-clock time. It's the classic flaky test. The busy-wait variant (poll a flag in a loop) is equally non-deterministic. The correct signal is the completion of the *actual* work.\n\n## Pattern 1: expose a TaskCompletionSource from the hosted service\n\nHave the service signal when its work is truly done, then await that signal. In the test, use a `TaskCompletionSource` the test controls.\n\n```csharp\npublic sealed class BackgroundWorker : IHostedService\n{\n public TaskCompletionSource<bool> Completed { get; } =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n private CancellationTokenSource? _cts;\n private Task? _running;\n\n public Task StartAsync(CancellationToken cancellationToken)\n {\n _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);\n _running = RunAsync(_cts.Token);\n return Task.CompletedTask;\n }\n\n private async Task RunAsync(CancellationToken token)\n {\n try\n {\n // ... do the background work ...\n await DoWorkAsync(token);\n }\n finally\n {\n Completed.TrySetResult(true); // signals \"finished\", success or not\n }\n }\n\n public async Task StopAsync(CancellationToken cancellationToken)\n {\n _cts?.Cancel();\n if (_running is not null)\n await _running; // let the loop observe cancellation\n }\n}\n```\n\nTest:\n\n```csharp\n[Fact]\npublic async Task Worker_CompletesWork()\n{\n var worker = new BackgroundWorker(/* seams */);\n\n await worker.StartAsync(CancellationToken.None);\n\n // Await the real completion, with a test timeout so a hang fails loudly.\n var finished = await Task.WhenAny(\n worker.Completed.Task,\n Task.Delay(TimeSpan.FromSeconds(5)));\n Assert.Same(worker.Completed.Task, finished); // fail if we hit the timeout\n\n // now assert on observable effects\n _repositoryMock.Verify(r => r.SaveAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Once);\n}\n```\n\nNote the `Task.WhenAny` with a timeout: if the work never completes, the test fails with a clear assertion instead of hanging the whole run. That's the only place a delay belongs — as an upper-bound guard, not as the synchronization mechanism.\n\n`RunContinuationsAsynchronously` matters: without it, `TrySetResult` can run your test's continuation synchronously on the background thread, which is a subtle source of surprises.\n\n## Pattern 2: if the worker is a queue consumer, await the effect not the worker\n\nIf the hosted service drains a channel/queue, don't reach into the worker's internals. Inject the queue, enqueue a message in the test, and await a completion signal the worker sets after processing each item — or, better, await a domain-level observable (e.g., the repository's `SaveAsync` was awaited). The principle: await the *effect* the test cares about.\n\n## Pattern 3: test the worker's logic directly, skip the host\n\nMost `IHostedService` tests should not spin up the host at all. Extract the loop body into a plain method/class and unit-test it directly:\n\n```csharp\n[Fact]\npublic async Task ProcessLoop_HandlesItem()\n{\n var processor = new WorkItemProcessor(_repositoryMock.Object);\n await processor.ProcessAsync(item, CancellationToken.None); // await the real Task\n _repositoryMock.Verify(...);\n}\n```\n\nYou get determinism for free because you control the await boundary. Reserve full-host integration tests (`WebApplicationFactory`, `IHost.StartAsync`) for wiring-level concerns, and even there await `host.StartAsync()`/`IHostedService.StopAsync()` rather than sleeping.\n\n## Rules that apply regardless of pattern\n\n- The test method is `async Task`, never `async void`, so the runner can await it and surface assertion failures.\n- Never block with `.Result`/`.Wait()` — you'll deadlock against single-threaded contexts and lose exception detail.\n- Make the completion signal the *real* end of the work (a `finally` around the actual loop, or a domain effect), not an arbitrary checkpoint mid-way.\n- If you need bidirectional coordination, use a real primitive (`TaskCompletionSource`, `SemaphoreSlim`, `Channel`), not a sleep or a spin.\n\n## Verification checklist before you call it done\n\n- `dotnet test` passes, and passes reliably when you run it several times in a row (no timer-dependent flakiness).\n- The synchronization point awaits the actual `Task`/signal the production code completes, not a delay.\n- Any `Task.Delay` present is only a `Task.WhenAny` timeout guard, and hitting it fails the test.\n- The test method is `async Task`, awaits the call under test, and contains no `Thread.Sleep`, `.Result`, `.Wait()`, or `async void`.",
874
+ "outputSha256": "54d2addd901af289f5d04a2cd41e68d1e93b78d6734e68c638cf0d56d59b9f3e",
875
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
876
+ "deterministic": [],
877
+ "judge": {
878
+ "verdict": "pass",
879
+ "reason": "Answer concretely shows a TaskCompletionSource (Completed.TrySetResult in finally) awaited via Task.WhenAny in the test, plus awaiting the real Task in Pattern 3. It explains timers are flaky because they depend on guessing machine load/timing. Task.Delay appears only as a timeout guard that fails the test, not as the wait mechanism."
880
+ },
881
+ "passed": true
882
+ },
883
+ {
884
+ "output": "The right way is to give the test something awaitable to wait on — not a timer. A `Task.Delay(...)` or `Thread.Sleep(...)` loop is non-deterministic and will flake under CI load; and `IHostedService.StartAsync` returning \"started\" doesn't mean the background work is done, because that method typically kicks off a fire-and-forget task and returns immediately.\n\nHere are the patterns that work, roughly in order of how much control you need:\n\n## 1. Expose an awaitable completion signal from the service\n\nThe cleanest fix is to make the service's work observable. Have the hosted service (or the worker it delegates to) expose the `Task` that represents the actual background work:\n\n```csharp\npublic class MyHostedService : BackgroundService\n{\n // BackgroundService already tracks the running ExecuteAsync task\n // via .ExecuteTask, but if you wrote a service by hand, expose it:\n public Task Completion { get; private set; }\n\n public override Task StartAsync(CancellationToken ct)\n {\n Completion = RunAsync(ct); // capture the real work\n return Task.CompletedTask; // do not await the work here\n }\n\n private async Task RunAsync(CancellationToken ct) { /* real work */ }\n}\n```\n\nThen in the test:\n\n```csharp\n[Fact]\npublic async Task DoesWork_Completes()\n{\n var sut = new MyHostedService(...);\n\n await sut.StartAsync(CancellationToken.None);\n await sut.Completion; // deterministic wait\n\n Assert.True(sut.DidTheWork);\n}\n```\n\nIf you're using ASP.NET Core's `BackgroundService`, note it already exposes a protected `ExecuteTask` that completes when `ExecuteAsync` finishes; in a real host you can `await` that, or better, wait on whatever downstream observable you actually care about (below).\n\n## 2. Wait on a completion primitive the work itself signals\n\nIf the service's job is to produce something observable, synchronize on that rather than on the service at all. A `TaskCompletionSource<bool>` the service sets when done is a common, framework-agnostic choice:\n\n```csharp\npublic class MyHostedService : BackgroundService\n{\n public TaskCompletionSource<bool> Completed { get; } =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n protected override async Task ExecuteAsync(CancellationToken ct)\n {\n try\n {\n await DoTheWorkAsync(ct);\n Completed.TrySetResult(true);\n }\n catch (Exception ex)\n {\n Completed.TrySetException(ex);\n }\n }\n}\n```\n\n```csharp\nawait sut.Completed.Task.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\n`WaitAsync(TimeSpan)` (.NET 6+) is the key ingredient: it turns an unbounded await into a *bounded, deterministic* await. On timeout it throws `TimeoutException`, so a hang fails the test with a clear message instead of blocking forever. Always prefer this over `Thread.Sleep`-and-poll.\n\n## 3. Prefer real synchronization over the IHostedService abstraction\n\nFor most tests, the hosted-service plumbing is noise. If the goal is \"the work eventually happens,\" skip `IHostedService` entirely in the test and test the worker method directly:\n\n```csharp\n[Fact]\npublic async Task Worker_ProcessesQueue()\n{\n var worker = new OrderWorker(repo, queue);\n await worker.ProcessOnceAsync(CancellationToken.None); // no background at all\n repo.Verify(r => r.Save(It.IsAny<Order>()), Times.Once);\n}\n```\n\nReserve an actual host-started background service test for the integration case, and there use `WebApplicationFactory` plus an observable side effect (a row written, a message on a channel) that you await with a bounded, polled `WaitAsync`.\n\n## 4. If you must poll (last resort)\n\nOnly when there is genuinely no completion signal to hook onto — e.g. a third-party service you can't modify — poll a real condition with a timeout, never an unconditional delay:\n\n```csharp\nawait WaitUntilAsync(() => repo.Count > 0, TimeSpan.FromSeconds(5));\n```\n\nwhere `WaitUntilAsync` repeatedly checks the condition, sleeping in small increments, and throws `TimeoutException` if the deadline passes. This is strictly worse than 1–3 because it's timing-dependent; reach for it only when you have no completion primitive.\n\n## Anti-patterns to avoid\n\n- `Thread.Sleep(200)` / `await Task.Delay(200)` then assert — flaky and proves nothing about ordering.\n- `async void` test methods — the runner can't await them, so failures are silently swallowed.\n- `task.Wait()` or `task.Result` — can deadlock on a captured synchronization context and provides no timeout.\n- Assuming `StartAsync` completing means the work finished — it almost never does.\n\nThe general rule: the unit's completion must be represented by an awaitable you can `await` with a timeout. If your service doesn't expose one, that's the thing to add — it's a small production change that makes the service testable, not test-only scaffolding.\n\nSince I don't have your service's code here, the specific choice depends on what the background operation actually produces: tell me whether it writes to a repository, a channel/queue, or just flips an in-memory flag, and I can point you at the exact completion signal to await.",
885
+ "outputSha256": "111d3a7c209611c5706e7e90001871071eb6fa4b20a6bec768777379c729d2ad",
886
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
887
+ "deterministic": [],
888
+ "judge": {
889
+ "verdict": "pass",
890
+ "reason": "Answer concretely shows awaiting the operation's own Task (`await sut.Completion;`) and a TaskCompletionSource (`sut.Completed.Task.WaitAsync(...)`), naming these as the join point. It explains fixed delays are non-deterministic/flaky and depend on guessing timing, and warns against Thread.Sleep/Task.Delay rather than recommending them."
891
+ },
892
+ "passed": true
893
+ },
894
+ {
895
+ "output": "The right way is to expose a real completion signal from the hosted service and await it — never poll or sleep. The exact mechanism depends on what \"finished\" means for your background work, but the shape is the same in every case: the service owns a `Task` (or `TaskCompletionSource`) representing its completion, and the test `await`s that, not a fixed delay.\n\n**1. `BackgroundService` with a `TaskCompletionSource` / exposed `Task`**\n\nIf your hosted service is a `BackgroundService`, `ExecuteAsync` is the work. Keep a handle to it that the test can await:\n\n```csharp\npublic sealed class MyHostedService : BackgroundService\n{\n private readonly TaskCompletionSource _started =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n // Completes once the service has done its one-shot work.\n public Task Started => _started.Task;\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n try\n {\n await DoTheWorkAsync(stoppingToken);\n _started.TrySetResult();\n }\n catch (Exception ex)\n {\n _started.TrySetException(ex); // surface failures to the test\n throw;\n }\n }\n}\n```\n\nTest:\n\n```csharp\nvar service = new MyHostedService(deps);\nawait service.StartAsync(CancellationToken.None);\nawait service.Started; // real completion, no sleep\n// ... now assert on whatever the service produced\nawait service.StopAsync(CancellationToken.None);\n```\n\nKey points:\n- `StartAsync` returns as soon as `ExecuteAsync` reaches its first `await`, so you still need `Started` to know the *work* is done — but that's a real signal, not a timer.\n- If the work can fail, `TrySetException` makes the test await throw the actual exception rather than timing out.\n- `TaskCreationOptions.RunContinuationsAsynchronously` avoids the test's continuation running inline on the service's thread.\n\n**2. Using a real `IHost` (`WebApplicationFactory` / `HostBuilder`)**\n\nIf you're testing through the host rather than the service instance directly, you can't easily get the `Task` back. Two options:\n\n- Resolve the service and await its published `Task`:\n ```csharp\n var service = host.Services.GetRequiredService<MyHostedService>();\n await service.Started;\n ```\n (This requires `MyHostedService` to be registered as the concrete type, not only as `IHostedService`.)\n- Or register a test-only hook so the test owns the signal:\n ```csharp\n var done = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);\n services.AddSingleton<IBackgroundCompletion>(new TestCompletion(done));\n // ...\n await done.Task;\n ```\n\n**3. If the work is naturally push-based (a queue, channel, event)**\n\nAwait the actual delivery rather than \"some time has passed\". For a `Channel<(T)>, for example, `await channel.Reader.ReadAsync(cts.Token)` — the read itself is the synchronization point.\n\n**What to avoid, and why**\n\n- `await Task.Delay(200)` — flaky by construction: passes on your machine, fails under CI load or a slow debugger, and there's no signal telling you the real work even ran.\n- `Thread.Sleep(...)` — same problem, plus it blocks a pool thread.\n- Polling a bool/flag in a loop — just a `Task.Delay` in disguise, with the same flakiness and no bound on total wait.\n- Awaiting `StopAsync` alone — that stops the service and completes the `ExecuteAsync` task, but if `ExecuteAsync` exits early (or swallows the cancellation) it says nothing about whether the unit of work finished; prefer an explicit completion signal.\n\n**Bound the wait so a hang fails fast**\n\nWhatever signal you await, give the test a timeout so a regression produces a clean failure instead of hanging the run:\n\n```csharp\nusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));\nawait service.Started.WaitAsync(cts.Token);\n```\n\n`Task.WaitAsync(CancellationToken)` (available on .NET 6+) throws `OperationCanceledException` on timeout rather than blocking forever.\n\nSo: make the hosted service publish its completion as a `Task` (or `TaskCompletionSource`), await that `Task` with a bounded timeout, and let any service failure propagate through the same task as an exception. That gives you determinism, real failure visibility, and no dependence on wall-clock timing.",
896
+ "outputSha256": "145779e7949bec9c2177f83fac9df6f2d6eb0e792f02cd990297275e09a35d50",
897
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
898
+ "deterministic": [],
899
+ "judge": {
900
+ "verdict": "pass",
901
+ "reason": "Answer concretely shows a TaskCompletionSource exposed as `public Task Started => _started.Task;` and the test awaiting `await service.Started;` as the join point (also `await done.Task`, `await channel.Reader.ReadAsync`). It explains fixed delays are flaky and depend on guessing timing, and warns against Task.Delay/Thread.Sleep rather than recommending them."
902
+ },
903
+ "passed": true
904
+ },
905
+ {
906
+ "output": "The right way is to make the background work signal its own completion, then await that signal in the test — not to poll or sleep for a fixed duration. The exact shape depends on what the `IHostedService` actually is, so here's the decision tree:\n\n**If you control the `IHostedService` (it's your class):**\nExpose a `Task` that completes when the operation finishes, and await it in the test.\n\n```csharp\npublic sealed class MyBackgroundService : BackgroundService\n{\n private readonly TaskCompletionSource _completed =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n // Test-visible hook; can be internal + InternalsVisibleTo.\n public Task Completion => _completed.Task;\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n try\n {\n await DoTheWorkAsync(stoppingToken);\n }\n finally\n {\n _completed.TrySetResult();\n }\n }\n}\n```\n\n```csharp\n[Fact]\npublic async Task BackgroundWork_CompletesWithinExpectedState()\n{\n var service = new MyBackgroundService(...);\n await service.StartAsync(CancellationToken.None);\n\n await service.Completion.WaitAsync(TimeSpan.FromSeconds(5)); // await the real signal\n\n // assertions now run only after the work actually finished\n Assert.Equal(expected, service.State);\n await service.StopAsync(CancellationToken.None);\n}\n```\n\nThe `WaitAsync(timeout)` (or `Task.WhenAny(task, Task.Delay(...))` on older TFMs) matters: if the signal never fires, you get a real, loud test failure instead of a hang that blocks CI forever. The delay used this way is a *watchdog*, not a synchronization primitive — that's a meaningfully different thing from \"sleep 200ms so the work probably finishes.\"\n\n**If the service writes to an observable sink (channel, queue, `IHostedService` that publishes events):**\nAwait on the sink instead of on the service:\n\n```csharp\n// service publishes to a Channel<Message>\nvar reader = channel.Reader;\nawait service.StartAsync(CancellationToken.None);\n\nusing var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));\nvar message = await reader.ReadAsync(cts.Token); // completes when the work produces output\n\nAssert.Equal(expected, message.Payload);\n```\n\n**If you're testing the whole host (`WebApplicationFactory<TEntryPoint>` / `HostBuilder`):**\nStart the host, then await whatever shared state the reasoning depends on — the same signal discussed above, or an endpoint/health check that only succeeds once the background work has caught up:\n\n```csharp\nawait using var app = new WebApplicationFactory<Program>();\nvar client = app.CreateClient();\n\n// poll a *real* readiness signal, with a watchdog and an overall timeout\nvar deadline = DateTime.UtcNow.AddSeconds(10);\nwhile (DateTime.UtcNow < deadline)\n{\n if ((await client.GetAsync(\"/health/ready\")).IsSuccessStatusCode) break;\n await Task.Delay(50);\n}\n```\n\nHere `Task.Delay` inside the retry loop is acceptable because you're polling a real readiness condition and asserting its truth — unlike a fixed sleep, the loop exits the instant the condition is met and fails loudly at the deadline.\n\n**If the service is not yours and exposes no completion signal:**\nThat's the hard case. Options, in order of preference:\n1. Wrap it behind your own seam (`IBackgroundWork`) so tests can substitute a deterministic implementation and await its completion — this is the correct fix if feasible.\n2. If it raises an event when done, use a `TaskCompletionSource` wired to that event: `void OnDone(...) => tcs.TrySetResult();`.\n3. As a last resort, poll an observable side effect (a row in a fake repository, a file, a log record) with a deadline-watchdog loop as above — never a bare `Thread.Sleep`/fixed `Task.Delay`.\n\n**Two anti-patterns to avoid explicitly:**\n- `Thread.Sleep(n)` / a bare `Task.Delay(n)` as the synchronization mechanism — flaky under load, and either too short (fails intermittently) or too long (slows the suite).\n- `async void` test methods. `[Fact]` async test methods must return `Task`; `async void` means the runner can't await the test and any assertion failure or exception inside it is silently swallowed. Always `public async Task` for async tests.",
907
+ "outputSha256": "3ebedefb7b1c2e1c944b1c01747b5a71790c6ada7e6f55b6a6f14bd835481881",
908
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
909
+ "deterministic": [],
910
+ "judge": {
911
+ "verdict": "pass",
912
+ "reason": "Answer names concrete join points: TaskCompletionSource `_completed` exposed as `Completion` and awaited via `await service.Completion.WaitAsync(...)`; also channel `reader.ReadAsync(cts.Token)` and TCS wired to event. It explains reliability: watchdog vs fixed sleep, 'exits the instant the condition is met', and warns against bare Thread.Sleep/Task.Delay as sync mechanism."
913
+ },
914
+ "passed": true
915
+ },
916
+ {
917
+ "output": "The right way is to await a real completion signal, not a fixed delay. Which signal depends on how you start the background work, so pick the shape below that matches your code. Do not use `Task.Delay` or `Thread.Sleep` for this — that is flaky under load (too short and it fails intermittently, too long and the suite is slow).\n\n## 1. If you control the hosted service, expose a completion handle\n\nThe cleanest option: give the service a `Task` (or `TaskCompletionSource`) that completes when its work loop finishes, and await it in the test. This is deterministic and fast.\n\n```csharp\npublic sealed class Worker : IHostedService\n{\n private readonly TaskCompletionSource _started =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n private readonly TaskCompletionSource _done =\n new(TaskCreationOptions.RunContinuationsAsynchronously);\n\n // The test awaits this instead of guessing how long the work takes.\n public Task Completion => _done.Task;\n\n public Task StartAsync(CancellationToken ct)\n {\n _ = RunAsync(ct);\n return Task.CompletedTask;\n }\n\n public Task StopAsync(CancellationToken ct) => Task.CompletedTask;\n\n private async Task RunAsync(CancellationToken ct)\n {\n _started.SetResult();\n // ...do the background work...\n _done.SetResult(); // signal the test\n }\n}\n```\n\nTest:\n\n```csharp\n[Fact]\npublic async Task Worker_ProcessesItem_WhenStarted()\n{\n var worker = new Worker(/* fakes for external seams */);\n await worker.StartAsync(CancellationToken.None);\n\n await worker.Completion.WaitAsync(TimeSpan.FromSeconds(5)); // bounded, not a sleep\n\n // assertions on the fake (e.g. repository/HTTP) now see the finished work\n repo.Verify(r => r.SaveAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()), Times.Once);\n}\n```\n\n**Important gotcha:** `StartAsync` on `IHostedService` is only *supposed* to start the work and return quickly — the host does not await the service's long-running work. So if your service swallows the task inside `StartAsync` without exposing it (like `_ = RunAsync()` above with no handle), the caller fundamentally cannot know when the work is done. Add the completion handle.\n\n## 2. If you start via the host (`IHost.StartAsync`)\n\nThe host does not wait for hosted-service background work either, so the same rule applies — you need a completion handle on the service itself. Get the instance back out of DI and await its handle:\n\n```csharp\nvar host = await new HostBuilder() /* ... */ .StartAsync();\nvar worker = host.Services.GetRequiredService<Worker>();\n\nawait worker.Completion.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\n`IHost` also gives you `StopAsync`, but that signals shutdown, not \"the current unit of work finished\" — use the explicit handle for that.\n\n## 3. If you only care that a side effect happened\n\nIf the background work's observable result is a write to a fake (repository, message bus, HTTP handler), await *that* instead of the service:\n\n```csharp\n// A TaskCompletionSource the fake completes when the expected call lands.\nawait fakeRepo.FirstSave.WaitAsync(TimeSpan.FromSeconds(5));\n```\n\nThis tests the actual contract (the side effect occurred) rather than the internal timing of the loop, which is usually what you want.\n\n## Why not the alternatives\n\n| Approach | Why it is wrong here |\n|---|---|\n| `await Task.Delay(200)` | Non-deterministic; passes locally, flakes in CI under load. Also silently \"passes\" whenever the work happened to finish early, so it never proves the wait mattered. |\n| `Thread.Sleep(200)` | Same problem, and blocks the thread inside an otherwise-async test. |\n| `await Task.Yield()` / `Task.Run` to force a turn | Only yields one scheduler turn; nowhere near a guarantee the background loop advanced. |\n| Polling in a `while` loop with sleeps | Reinvents a synchronization primitive badly; use a `TaskCompletionSource` the work sets. |\n\n## General rules\n\n- **Test methods are `async Task`, never `async void`** — with `async void` the runner cannot await the test, so a failed assertion or thrown exception inside it is silently lost instead of failing the test.\n- **Never block on `.Result`/`.Wait()`** in the test — `await` the handle.\n- **Bound the wait** with `WaitAsync(timeout)` (or a CTS) so a genuine hang fails the test with a clear timeout rather than hanging the whole run forever. The timeout is a safety net, not the synchronization mechanism — the happy path should complete in milliseconds.\n- **Do not replace this with a mocked internal collaborator.** If reaching for a mock makes the \"when is it done?\" question disappear (e.g. mocking the very worker under test), you have stopped testing the real background behavior. Mock only the external seams (repository, HTTP, clock) and signal completion through real task handles or those fakes.\n\nThe short version: expose or await a real `Task`/`TaskCompletionSource` that fires when the work finishes, wrap it in `WaitAsync(timeout)`, and never reach for `Task.Delay`/`Thread.Sleep` to bridge the gap.",
918
+ "outputSha256": "2c5b4837cb7d7fdf79a760047a8276c03650d4d79065bcf85ff54d225634f58d",
919
+ "promptSha256": "cad36ac800e8a70c4a6fe1112c5f1ce52b5c21f25f015401bb6c7ebf2327197f",
920
+ "deterministic": [],
921
+ "judge": {
922
+ "verdict": "pass",
923
+ "reason": "Answer concretely shows a TaskCompletionSource (`_done`) exposed as `Completion` and awaited via `await worker.Completion.WaitAsync(...)`, plus awaiting a fake's TCS. It explains fixed delays are non-deterministic/flaky under load. Thread.Sleep/Task.Delay only warned against, not recommended."
924
+ },
925
+ "passed": true
926
+ }
927
+ ]
928
+ }
929
+ ],
930
+ "verdict": "fail",
931
+ "scope": "bundled",
932
+ "skillDigest": "52ae0b1b28ffc1e8bd72ad8f43282650b038cb5937e7859eb01098e7db2e2bda",
933
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
934
+ "judgePromptVersion": "2026-09-25.1",
935
+ "runner": "deepseek",
936
+ "model": "deepseek-chat",
937
+ "runnerPromptVersion": "2026-09-25.1",
938
+ "recordedAt": "2026-09-25T18:13:00.500Z",
939
+ "judge": "deepseek",
940
+ "judgeModel": "deepseek-chat"
941
+ },
942
+ {
943
+ "schemaVersion": "1.0.0",
944
+ "skillId": "csharp-dotnet/dotnet-code-review",
945
+ "strictness": "high",
946
+ "trials": 10,
947
+ "triggerAccuracy": {
948
+ "truePositive": 3,
949
+ "falsePositive": 0,
950
+ "positives": 7,
951
+ "negatives": 8
952
+ },
953
+ "evidence": "authored",
954
+ "scenarios": [
955
+ {
956
+ "id": "trigger-positive-1",
957
+ "kind": "trigger-positive",
958
+ "prompt": "Check this pull request for fire-and-forget async methods that could crash",
959
+ "strictness": "high",
960
+ "trials": 1,
961
+ "passes": 1,
962
+ "passRate": 1,
963
+ "passAtK": 1,
964
+ "grader": "trigger-rank-fork-family",
965
+ "status": "ran",
966
+ "deterministic": true
967
+ },
968
+ {
969
+ "id": "trigger-positive-2",
970
+ "kind": "trigger-positive",
971
+ "prompt": "Does this C# change block a thread waiting on a Task anywhere?",
972
+ "strictness": "high",
973
+ "trials": 1,
974
+ "passes": 0,
975
+ "passRate": 0,
976
+ "passAtK": 0,
977
+ "grader": "trigger-rank-fork-family",
978
+ "status": "ran",
979
+ "deterministic": true
980
+ },
981
+ {
982
+ "id": "trigger-positive-3",
983
+ "kind": "trigger-positive",
984
+ "prompt": "Look over this diff for a database context leaking into a long-lived service",
985
+ "strictness": "high",
986
+ "trials": 1,
987
+ "passes": 0,
988
+ "passRate": 0,
989
+ "passAtK": 0,
990
+ "grader": "trigger-rank-fork-family",
991
+ "status": "ran",
992
+ "deterministic": true
993
+ },
994
+ {
995
+ "id": "trigger-positive-4",
996
+ "kind": "trigger-positive",
997
+ "prompt": "Audit this .NET change for exception handling that hides real failures",
998
+ "strictness": "high",
999
+ "trials": 1,
1000
+ "passes": 1,
1001
+ "passRate": 1,
1002
+ "passAtK": 1,
1003
+ "grader": "trigger-rank-fork-family",
1004
+ "status": "ran",
1005
+ "deterministic": true
1006
+ },
1007
+ {
1008
+ "id": "trigger-positive-5",
1009
+ "kind": "trigger-positive",
1010
+ "prompt": "Any missing using/await using in this controller change?",
1011
+ "strictness": "high",
1012
+ "trials": 1,
1013
+ "passes": 0,
1014
+ "passRate": 0,
1015
+ "passAtK": 0,
1016
+ "grader": "trigger-rank-fork-family",
1017
+ "status": "ran",
1018
+ "deterministic": true
1019
+ },
1020
+ {
1021
+ "id": "trigger-positive-6",
1022
+ "kind": "trigger-positive",
1023
+ "prompt": "Review this code change for nullable annotation holes before merge",
1024
+ "strictness": "high",
1025
+ "trials": 1,
1026
+ "passes": 1,
1027
+ "passRate": 1,
1028
+ "passAtK": 1,
1029
+ "grader": "trigger-rank-fork-family",
1030
+ "status": "ran",
1031
+ "deterministic": true
1032
+ },
1033
+ {
1034
+ "id": "trigger-positive-7",
1035
+ "kind": "trigger-positive",
1036
+ "prompt": "Take a look at this diff, read-only, for resource cleanup issues",
1037
+ "strictness": "high",
1038
+ "trials": 1,
1039
+ "passes": 0,
1040
+ "passRate": 0,
1041
+ "passAtK": 0,
1042
+ "grader": "trigger-rank-fork-family",
1043
+ "status": "ran",
1044
+ "deterministic": true
1045
+ },
1046
+ {
1047
+ "id": "trigger-negative-1",
1048
+ "kind": "trigger-negative",
1049
+ "prompt": "Review this Go diff for goroutine leaks and data races",
1050
+ "strictness": "high",
1051
+ "trials": 1,
1052
+ "passes": 1,
1053
+ "passRate": 1,
1054
+ "passAtK": 1,
1055
+ "grader": "trigger-rank-fork-family",
1056
+ "status": "ran",
1057
+ "deterministic": true
1058
+ },
1059
+ {
1060
+ "id": "trigger-negative-2",
1061
+ "kind": "trigger-negative",
1062
+ "prompt": "Review this Python code for SQL injection",
1063
+ "strictness": "high",
1064
+ "trials": 1,
1065
+ "passes": 1,
1066
+ "passRate": 1,
1067
+ "passAtK": 1,
1068
+ "grader": "trigger-rank-fork-family",
1069
+ "status": "ran",
1070
+ "deterministic": true
1071
+ },
1072
+ {
1073
+ "id": "trigger-negative-3",
1074
+ "kind": "trigger-negative",
1075
+ "prompt": "Review this TypeScript diff for missing null checks",
1076
+ "strictness": "high",
1077
+ "trials": 1,
1078
+ "passes": 1,
1079
+ "passRate": 1,
1080
+ "passAtK": 1,
1081
+ "grader": "trigger-rank-fork-family",
1082
+ "status": "ran",
1083
+ "deterministic": true
1084
+ },
1085
+ {
1086
+ "id": "trigger-negative-4",
1087
+ "kind": "trigger-negative",
1088
+ "prompt": "Review this Swift diff for retain cycles and force-unwraps",
1089
+ "strictness": "high",
1090
+ "trials": 1,
1091
+ "passes": 1,
1092
+ "passRate": 1,
1093
+ "passAtK": 1,
1094
+ "grader": "trigger-rank-fork-family",
1095
+ "status": "ran",
1096
+ "deterministic": true
1097
+ },
1098
+ {
1099
+ "id": "trigger-negative-5",
1100
+ "kind": "trigger-negative",
1101
+ "prompt": "Review this Kotlin diff for coroutine scope leaks",
1102
+ "strictness": "high",
1103
+ "trials": 1,
1104
+ "passes": 1,
1105
+ "passRate": 1,
1106
+ "passAtK": 1,
1107
+ "grader": "trigger-rank-fork-family",
1108
+ "status": "ran",
1109
+ "deterministic": true
1110
+ },
1111
+ {
1112
+ "id": "trigger-negative-6",
1113
+ "kind": "trigger-negative",
1114
+ "prompt": "Review this Flutter diff for setState calls after dispose",
1115
+ "strictness": "high",
1116
+ "trials": 1,
1117
+ "passes": 1,
1118
+ "passRate": 1,
1119
+ "passAtK": 1,
1120
+ "grader": "trigger-rank-fork-family",
1121
+ "status": "ran",
1122
+ "deterministic": true
1123
+ },
1124
+ {
1125
+ "id": "trigger-negative-7",
1126
+ "kind": "trigger-negative",
1127
+ "prompt": "Implement a new C# endpoint that queries the order repository",
1128
+ "strictness": "high",
1129
+ "trials": 1,
1130
+ "passes": 1,
1131
+ "passRate": 1,
1132
+ "passAtK": 1,
1133
+ "grader": "trigger-rank-fork-family",
1134
+ "status": "ran",
1135
+ "deterministic": true
1136
+ },
1137
+ {
1138
+ "id": "trigger-negative-8",
1139
+ "kind": "trigger-negative",
1140
+ "prompt": "dotnet test is failing on this project, fix it",
1141
+ "strictness": "high",
1142
+ "trials": 1,
1143
+ "passes": 1,
1144
+ "passRate": 1,
1145
+ "passAtK": 1,
1146
+ "grader": "trigger-rank-fork-family",
1147
+ "status": "ran",
1148
+ "deterministic": true
1149
+ },
1150
+ {
1151
+ "id": "async-void-review",
1152
+ "kind": "behavior",
1153
+ "prompt": "Review this C# diff: a new method was added as `public async void ProcessOrderAsync(Order order)` on OrderController, and it's called from a regular controller action (not a UI event handler). What do you find?",
1154
+ "strictness": "high",
1155
+ "trials": 10,
1156
+ "passes": 10,
1157
+ "passRate": 1,
1158
+ "passAtK": 1,
1159
+ "grader": "judge",
1160
+ "status": "ran",
1161
+ "trialRecords": [
1162
+ {
1163
+ "output": "**Finding — `src/.../OrderController.cs` (`public async void ProcessOrderAsync(Order order)`)**\n\n**Pattern:** `async void` on a method that is not a genuine event handler. It is called from a regular controller action, not from a UI event subscription, so it does not fall into the one legitimate `async void` case.\n\n**Why it matters:** For an `async void` method, the caller cannot `await` it — the method returns to the caller at the first `await`, and the caller has no `Task` handle to await, observe, or attach a continuation to. Any exception that escapes `ProcessOrderAsync` (after the first suspension point) is not captured in a `Task` the caller can catch; it is raised on the synchronization context / thread pool and, in ASP.NET Core, typically tears down the request or the process rather than surfacing as a catchable, handled exception at the call site. This defeats the controller action's `try`/`catch`. Concretely:\n\n- The controller action returns to the response pipeline before `ProcessOrderAsync` finishes. Work can keep running after the response is sent, or be aborted when the request scope is disposed — you get a race, not ordering.\n- An exception inside `ProcessOrderAsync` (e.g., a failed DB write) becomes an unhandled exception rather than a caught-and-mapped `500`/validation error. In ASP.NET Core this can crash the process (unhandled exception on a thread-pool thread) and is very hard to reproduce reliably.\n- Because there is no `Task`, tests cannot `await` the method to know when its work is done; the behavior is only deterministic by accident.\n\n**Fix direction (report only, not applied):** Change the signature to `public async Task ProcessOrderAsync(Order order)` and `await` it from the calling controller action, so the action's `try`/`catch` observes exceptions and the response is not completed until the work finishes. If the intent is genuinely fire-and-forget background work, that is a different design (a hosted/background service or a queue), not `async void` — flag the intent so that decision is made explicitly rather than by accident.\n\n**Also check while reviewing this member (Step 2 focus list), not yet confirmed:**\n\n- Whether `ProcessOrderAsync` or its call path contains blocking `.Result` / `.Wait()` / `.GetAwaiter().GetResult()` on a `Task` — sync-over-async deadlock risk, especially if any code above it assumes a synchronization context.\n- Whether any `IDisposable`/`IAsyncDisposable` created inside it (a `DbContext`, `HttpClient`, stream) lacks `using`/`await using` or a matching `Dispose` on the owning type.\n- Nullability of the `Order` parameter and any return path — a missing annotation that is later dereferenced without a check, or a `!` null-forgiving operator with no justification comment.\n\n**Certainty note:** The `async void` classification and its consequences are determinable from the signature and the stated call site alone, so this is asserted directly. If there were a suspicion of an actual deadlock from a `.Result` call inside, I would say to reproduce under load rather than assert it — but that is not this finding. No source file was modified; this is report-only.\n\n**Severity:** High — the `async void` signature is a correctness/crash defect introduced by this diff, not a pre-existing issue or a style nit.",
1164
+ "outputSha256": "088008dd66fca5b6d7ac2ee75ab4bbacd4ce92377a37024813afbb72d1392df7",
1165
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1166
+ "deterministic": [],
1167
+ "judge": {
1168
+ "verdict": "pass",
1169
+ "reason": "Answer names `async void` on ProcessOrderAsync and states it's called from a regular controller action, not a UI event handler (criterion 1). It explains the exception bypasses the caller's try/catch and can crash the process / be unobservable (criterion 2). Fix direction given: change to `public async Task ProcessOrderAsync` and await it, explicitly 'report only, not applied' and 'No source file was modified' (criterion 3). No edit claimed."
1170
+ },
1171
+ "passed": true
1172
+ },
1173
+ {
1174
+ "output": "**Finding: `async void` misuse**\n\n`src/Controllers/OrderController.cs` (new method) — `public async void ProcessOrderAsync(Order order)` is declared `async void` but is not a genuine event handler; it's called from a regular controller action. This is the classic `async void` misuse pattern.\n\n**Why it matters**\n\n- **Unobservable completion.** The caller cannot `await` an `async void` method — `await ProcessOrderAsync(order)` won't even compile against a `void` return. The calling action has no way to know when `ProcessOrderAsync` has finished, so it can (and almost certainly will) complete and return a response *before* the order processing has actually run. The caller's ability to sequence work, retry, or surface a failure is gone.\n- **Uncatchable exceptions crash the process.** An `async void` method runs on the ambient `SynchronizationContext` captured at the point of the first `await`. If it's an ASP.NET Core request context, there is no `SynchronizationContext` to marshal back to by default, so any exception thrown after the first await is raised on a thread-pool thread and is **not** routed back to the caller's `try`/`catch`. Depending on how the fault surfaces that becomes an unhandled-exception crash rather than a 500 response — the outer controller action's `try`/`catch` will never see it.\n- **Swallowed/fire-and-forget semantics are not what was likely intended.** A controller method named `...Async` on `OrderController` almost certainly intends to do work the request should wait on and whose failure the request should report. `async void` silently converts that into fire-and-forget, with no `Task` to track or await.\n\n**Fix direction (not applied — this review is read-only)**\n\n- Change the signature to `public async Task ProcessOrderAsync(Order order)`.\n- In the calling controller action, `await ProcessOrderAsync(order);` so the action's `async Task<IActionResult>` completes only after the order work is done and any exception propagates normally to be handled/logged and translated into a response.\n- If the intent really is fire-and-forget (it does not appear to be, given the name and placement), then: name it accordingly, return `Task` (never `async void`), do **not** `await` it at the call site but capture the `Task` and attach a continuation/`try`/`catch` for logging, and consider a durable queue or `IHostedService`/background job instead of an unawaited in-request task — an unawaited `Task` that outlives the request can be cancelled or its scope disposed mid-flight.\n\n**Note for the author:** `async void` is only acceptable on a true event-handler signature (`void` return is mandated by the event delegate, e.g. `void OnClick(object, EventArgs)`). A controller action or helper is never that case.\n\n**Verification note:** I can't prove the exact runtime behavior (process crash vs. abandoned task) from the signature alone — it depends on the request's `SynchronizationContext` and the host's unhandled-exception policy. Reproduce with a deliberately-thrown exception inside `ProcessOrderAsync` after an `await` under the target host to confirm which failure mode you get; the fix is the same either way.\n\nThis review did not modify any files. It covers the one method described; I did not see the calling action's body, so confirm there is no existing `await`/`Result` on it that would also need updating alongside the signature change.",
1175
+ "outputSha256": "1a7f7ac3fcd40529a937f33902f54acf705876fe603c19a70defdf5dd9dc5567",
1176
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1177
+ "deterministic": [],
1178
+ "judge": {
1179
+ "verdict": "pass",
1180
+ "reason": "Criterion 1: names `public async void ProcessOrderAsync(Order order)` as not a genuine event handler, called from a regular controller action. Criterion 2: states exceptions after await are not routed to caller's try/catch, becoming an unhandled-exception crash rather than a 500 response. Criterion 3: fix direction given (change to `async Task`, caller `await`), explicitly 'not applied — this review is read-only' and 'did not modify any files'. No edit claimed."
1181
+ },
1182
+ "passed": true
1183
+ },
1184
+ {
1185
+ "output": "src/Controllers/OrderController.cs — `public async void ProcessOrderAsync(Order order)` is an `async void` method that is not an event handler. Risk: `async void` methods cannot be awaited by their caller, so the calling controller action returns to ASP.NET Core the moment `ProcessOrderAsync` hits its first `await` — the request completes (and its response is written) while the work is still running, or worse, the request scope and any scoped/request-lifetime services it captured (e.g. the `DbContext`, `IHttpContextAccessor`, the injected scoped dependencies) may be disposed out from under the still-running continuation. Separately, any exception that escapes the method body bypasses the caller's `try`/`catch` entirely and is raised on the thread pool as an unhandled exception — in ASP.NET Core this tears down the request/connection and can crash the process, rather than producing a normal 500 at the call site. That \"fire-and-forget with no way to observe completion or failure\" behavior is the core defect, distinct from the exception routing.\n\nFix direction: change the signature to `public async Task ProcessOrderAsync(Order order)` (or `async Task<OrderResult>` if a result is needed) and `await` it from the controller action, so the action does not return until the work completes and failures propagate normally through the action's exception handling. If the work genuinely must be backgrounded after the response is sent, that is a different design — the caller should not `await` a discarded `Task`; hand off to a hosted service / background queue (`IHostedService`, `Channel<T>`, `BackgroundService`) that owns its own scope (`IServiceScopeFactory`) and does not capture the request's scoped services.\n\nTwo things I'd confirm while reviewing the rest of the diff rather than assert blind:\n\n- Whether the method body actually `await`s anything at all. If it is `async void` but contains no `await`, this is not a timing problem yet — but it is a latent one: the moment anyone adds an `await`, the fire-and-forget hazard above becomes real, so it should still be fixed now.\n- Whether the body performs any `IDisposable`/`IAsyncDisposable` work or captures scoped DI services (a `DbContext`, in particular). If it does, the lifetime mismatch compounds the un-awaited-`Task` problem — the scoped instance can be disposed mid-continuation. Flag that as a related finding once the body is read; confirm empirically by reproducing under concurrent load rather than asserting the disposal race from the signature alone.\n\nThis is diff-introduced, not pre-existing: the method is new on the controller and is called from a normal action, so it is in scope. No file was modified by this review — findings only. Suggested report line:\n\n```\nsrc/Controllers/OrderController.cs:<line> — async void ProcessOrderAsync is\nnot an event handler. Risk: caller cannot await it, so the action can\ncomplete (and dispose request-scoped services) while work is still\nrunning; escaped exceptions become unhandled thread-pool exceptions\ninstead of normal action failures. Fix direction: return Task and await\nit, or move genuine background work to a hosted service that owns its\nown scope.\n```",
1186
+ "outputSha256": "eb8bc973e03fcd565f33d87b97d358b1f2464b44e1ea6e519ba33b8a3cb12962",
1187
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1188
+ "deterministic": [],
1189
+ "judge": {
1190
+ "verdict": "pass",
1191
+ "reason": "Answer names `async void ProcessOrderAsync` as not an event handler (OrderController.cs line), states escaped exceptions bypass caller's try/catch and become unhandled thread-pool exceptions that can crash the process instead of a normal 500, and gives fix direction: change to `async Task` and await from the action. It explicitly says 'No file was modified by this review — findings only,' so no edit is claimed."
1192
+ },
1193
+ "passed": true
1194
+ },
1195
+ {
1196
+ "output": "src/Controllers/OrderController.cs — `public async void ProcessOrderAsync(Order order)` is `async void` and is not an event handler. Risk: exceptions it throws cannot be caught by any caller's `try`/`catch` (there is no `Task` to await or observe), so an unhandled exception propagates to the synchronization context / thread pool and crashes the process rather than being handled by the caller. Fix direction: change the signature to `public async Task ProcessOrderAsync(Order order)` and `await` it from the calling controller action.\n\nAdditional risk to flag while reviewing this change, since the same method body will contain them once the signature is corrected:\n- If `ProcessOrderAsync` internally blocks on other async work with `.Result`/`.Wait()`/`.GetAwaiter().GetResult()`, that is a sync-over-async deadlock risk under load — flag it as a separate finding. (Not certain from the diff alone — reproduce under load to confirm rather than assuming.)\n- A blanket `catch (Exception)` with no rethrow/logging inside the method body would swallow real failures silently regardless of the `async void` fix — flag if present.\n- Any non-event-handler caller invoking `ProcessOrderAsync` without awaiting is itself a fire-and-forget call site; once the method returns `Task`, the caller must `await` it, not discard it.\n\nFix direction is reported only — no edits applied. Confirm by reproducing the unhandled-exception crash path (e.g., throw from within `ProcessOrderAsync`) before treating it as confirmed rather than theoretical.",
1197
+ "outputSha256": "539573dc665fbc8b63a871aabd581fab0ff599e7e645010dd3cdc143c1099a59",
1198
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1199
+ "deterministic": [],
1200
+ "judge": {
1201
+ "verdict": "pass",
1202
+ "reason": "Answer identifies `async void` on ProcessOrderAsync and states it is not an event handler. It explains the concrete risk: exceptions can't be caught by caller's try/catch, propagate to sync context/thread pool and crash the process. Fix direction given: change to `public async Task ProcessOrderAsync` and await from caller. Explicitly states 'no edits applied' — read-only respected."
1203
+ },
1204
+ "passed": true
1205
+ },
1206
+ {
1207
+ "output": "**Finding — `src/Controllers/OrderController.cs` (new `ProcessOrderAsync` method) / its call site in the action method — `async void` on a non-event-handler method.**\n\nPattern: `public async void ProcessOrderAsync(Order order)` — `async void` used on a normal callable method rather than a genuine event handler.\n\nWhy it matters: exceptions thrown inside an `async void` method cannot be caught by the caller. They are raised on the captured `SynchronizationContext` (or an unhandled-exception path if there is none) rather than surfacing at the `await` point in the calling action. The regular controller action that calls `ProcessOrderAsync` therefore cannot observe a failure — the request may complete and return a success response even though processing failed partway through, and the faulted exception will escape as an unhandled exception that can crash the process. This is the classic `async void` hazard, and it is worse in an ASP.NET Core controller because there is no UI event-handler contract that would justify `async void` in the first place. There is also no way for the action to await completion, so ordering/timing of the response relative to the work is undefined.\n\nFix direction: change the signature to `public async Task ProcessOrderAsync(Order order)` and have the calling action `await` it (`await ProcessOrderAsync(order);`). If the caller is not otherwise `async`, make the action `async Task<IActionResult>` and await there. That restores normal exception propagation to the caller's `try`/`catch`/middleware and gives the action a deterministic completion point.\n\nTwo adjacent checks worth confirming while you are here (not asserted, since I can only see the snippet you quoted):\n\n- **Fire-and-forget conflation** — if the intent was to run the order processing in the background, `async void` is not the tool. Report direction: use `await` on the `Task`, or if truly backgrounded, a deliberate queued/hosted-service mechanism, never `async void`.\n- **DI lifetime in the new path** — if `ProcessOrderAsync` resolves a `DbContext` or other `Scoped` service into a `Singleton` field as part of this change, that is a separate finding (Scoped-into-Singleton capture). Not established from the diff text alone.\n\nThis is a diff-introduced issue, not pre-existing. This review is read-only; no file was modified — apply the `Task`-returning signature change in the source yourself.",
1208
+ "outputSha256": "27311c8305276a893a838d662e556e5d306cf0ddc2780fc862ee94b08d7be5b5",
1209
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1210
+ "deterministic": [],
1211
+ "judge": {
1212
+ "verdict": "pass",
1213
+ "reason": "Answer names `async void` on ProcessOrderAsync as the problem and notes it's not a genuine event handler ('no UI event-handler contract'). It states the concrete risk: exceptions can't be caught by caller, escape as unhandled and 'can crash the process', request may return success despite failure. Fix direction given: change to `public async Task ProcessOrderAsync` and have caller `await` it. Explicitly read-only: 'no file was modified'. No code change claimed."
1214
+ },
1215
+ "passed": true
1216
+ },
1217
+ {
1218
+ "output": "Finding: `src/.../OrderController.cs` — `public async void ProcessOrderAsync(Order order)` is not a UI event handler, so this is `async void` misuse.\n\nWhy it matters — crash risk: an `async void` method cannot be awaited by its caller, so the calling controller action returns the moment `ProcessOrderAsync` hits its first real `await` — any code after that await (including the order-processing result) runs detached from the request. Worse, if `ProcessOrderAsync` throws at any point after that first await, the exception has no `Task` to ride on and no `try`/`catch` at the call site can observe it; it is rethrown on the synchronization context / thread pool and crashes the process (in ASP.NET Core, typically taking down the app) instead of returning a 500 for that one request.\n\nFix direction (do not apply here): change the signature to `public async Task ProcessOrderAsync(Order order)`, and have the controller action `await ProcessOrderAsync(order)` before returning. `async void` is only defensible for a method whose signature is exactly an event handler's (`void Handler(object sender, EventArgs e)`), which this is not.\n\nAdditional checks worth confirming in the same diff, since they often accompany this pattern:\n- If the calling action currently does `ProcessOrderAsync(order);` without `await` and then returns, that call site also needs the `await` added once the return type becomes `Task`; otherwise the method becomes fire-and-forget with the same lost-exception problem even after the signature fix.\n- If any surrounding code reaches `.Result`/`.Wait()` on a `Task` returned from order processing, flag it as sync-over-async deadlock risk.\n- If `ProcessOrderAsync` opens a `DbContext` or other `IDisposable`, confirm it is in a `using`/`await using` — an `async void` method that is abandoned mid-flight is a common way for a disposal scope to be leaked.\n\nThis is read-only review output — no source file was modified.",
1219
+ "outputSha256": "6ff176831858d33177efaac7cbd146d6468658e4c711d7cdff8d8b9d92567c55",
1220
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1221
+ "deterministic": [],
1222
+ "judge": {
1223
+ "verdict": "pass",
1224
+ "reason": "Answer identifies `public async void ProcessOrderAsync(Order order)` as misuse, explicitly noting it 'is not a UI event handler' (crit.1). It states the concrete risk: exception 'has no Task to ride on and no try/catch at the call site can observe it... crashes the process... instead of returning a 500' (crit.2). Fix direction given: change to `public async Task ProcessOrderAsync` and have caller `await` it, marked 'do not apply here' and 'no source file was modified' (crit.3). No edit claimed."
1225
+ },
1226
+ "passed": true
1227
+ },
1228
+ {
1229
+ "output": "**Finding — `async void` misuse**\n\n`OrderController.ProcessOrderAsync(Order order)` — a new `public async void` method called from a regular controller action (not a UI event handler).\n\nRisk: any exception thrown inside `ProcessOrderAsync` after its first `await` does not flow back to the caller's `try`/`catch` or to the ASP.NET Core request pipeline in the normal way. Instead it propagates on an unobserved async context — typically surfacing as an unhandled exception that can crash the process, or at minimum bypass whatever error handling the calling action expects. It also means the calling action cannot `await` it, so:\n\n- The action returns before `ProcessOrderAsync` completes; the response can be sent while work is still running (or, on request-scoped services, after the DI scope has been disposed — see the DI note below), so the caller has no way to know the operation actually finished or succeeded.\n- The caller cannot observe completion, so it can't sequence any follow-up work or propagate failure to the client.\n\nFix direction: change the signature to `public async Task ProcessOrderAsync(Order order)` and `await` it from the calling controller action (which should itself be `async Task`). `async void` is only appropriate for actual event handlers, where the framework signature forces it — this is a plain public method with an ordinary caller, so it is not that case.\n\n---\n\n**Related thing to confirm while you're in this file (not necessarily a finding from the diff alone)**\n\nBecause the action no longer awaits the work, if `ProcessOrderAsync` touches a Scoped dependency (most commonly a `DbContext`), the request scope may be torn down and the context disposed before the background work runs. That would show up as `ObjectDisposedException` intermittently under load — worth checking whether the method captures `DbContext`/Scoped services, and if so, the fix is the same (await the work in the action, or move it to a properly scoped background service rather than fire-and-forget from the request).\n\n**To confirm rather than assert:** if you want certainty that this crashes rather than merely being non-deterministic, reproduce under load with an injected failure path after the first `await` and observe the process-level exception — I'm reading the diff, not running it.\n\nNo files were modified — findings and fix direction only.",
1230
+ "outputSha256": "af10e526c36069941f2df7ab5c23930a4d1b5167ed16efb6e597d5ce2b0aed76",
1231
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1232
+ "deterministic": [],
1233
+ "judge": {
1234
+ "verdict": "pass",
1235
+ "reason": "Answer identifies `public async void ProcessOrderAsync` called from a regular controller action, not a UI event handler (criterion 1). It states exceptions after the first await bypass the caller's try/catch and can crash the process or be unobserved (criterion 2). Fix direction given: change to `public async Task ProcessOrderAsync` and await it from the action (criterion 3). It explicitly says 'No files were modified — findings and fix direction only,' so no fail criterion holds."
1236
+ },
1237
+ "passed": true
1238
+ },
1239
+ {
1240
+ "output": "**Finding: `async void` misuse — real bug, introduced by this diff.**\n\n`OrderController.ProcessOrderAsync(Order order)` — declared `public async void` but is not a UI event handler; it's called from a regular controller action.\n\n**Why it matters (risk category: async void misuse / unobserved exception):**\n- Any exception thrown inside the method body (or by an `await` inside it) is not captured by the caller's `Task`. An `async void` method's exceptions are re-thrown on the ambient `SynchronizationContext` (or the thread pool if there is none) rather than surfacing at the call site. In ASP.NET Core there is effectively no UI-style `SynchronizationContext` to catch them, so it becomes an unhandled exception that can crash the process — the caller's `try`/`catch`, if any, will never see it.\n- The caller cannot await the operation. Whatever the calling controller action does next (return a response, complete the request, dispose a scoped `DbContext`, etc.) races against `ProcessOrderAsync`'s continuation. If `ProcessOrderAsync` touches a request-scoped service after the request completes, that's a use-after-dispose waiting to happen; at minimum, the work is not guaranteed to finish before the response is sent.\n- The method is also unreferenced by an event handler signature, so the usual \"async void is only for event handlers\" justification does not apply.\n\n**Fix direction (not applied — this review is read-only):**\n- Change the signature to `public async Task ProcessOrderAsync(Order order)` and `await` the call from the controller action (or return the task directly where the action is itself `async`).\n- If the intent really is fire-and-forget background work, that is a *different* decision: it should be explicit — e.g. enqueue onto a hosted/background queue (`IHostedService`, `Channel<T>`, Hangfire/`BackgroundService`) rather than `async void` — and the scoped `Order`/DbContext services must not be captured into that background work.\n\n**One pre-existing/adjacent check worth doing when you read the surrounding code:** confirm whether the calling action injects a scoped service (most commonly a `DbContext`) that `ProcessOrderAsync` reads after the request may have finished — if so, that is a second, related finding (scope lifetime mismatch), reportable separately from this diff's `async void` issue.\n\nNote: I only have the signature and call-site description above, not the body of `ProcessOrderAsync` — I did not inspect the method body for further issues (blocking calls, `ValueTask` misuse, disposal, etc.). Send the full method body and its call site if you want those checked too.",
1241
+ "outputSha256": "a3b6ae990a0379e1f9d44805b1c70b9a9e95116e0660091db280294dac951381",
1242
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1243
+ "deterministic": [],
1244
+ "judge": {
1245
+ "verdict": "pass",
1246
+ "reason": "Answer identifies `public async void ProcessOrderAsync` as the bug and states it is not a UI event handler ('not a UI event handler; it's called from a regular controller action'). It gives the concrete risk: exceptions re-thrown on SynchronizationContext, unhandled, 'can crash the process — the caller's try/catch... will never see it.' Fix direction given: change to `public async Task ProcessOrderAsync` and await from caller, explicitly 'not applied — this review is read-only.' No code change made."
1247
+ },
1248
+ "passed": true
1249
+ },
1250
+ {
1251
+ "output": "**Finding — `async void` on a non-event-handler method.**\n\n`OrderController.ProcessOrderAsync(Order order)` — declared `public async void` but called from a regular controller action, not a UI/event-handler context.\n\n**Risk:** `async void` has no `Task` for a caller to await or observe. If `ProcessOrderAsync` throws after its first `await`, the exception is raised on the captured `SynchronizationContext` (or an unhandled thread-pool thread) rather than propagating to the calling controller action. In ASP.NET Core that typically surfaces as an unhandled exception crashing the request/process — the caller's `try`/`catch` around the call site will not see it. It also means the calling action cannot know when the work actually completes, so any request-scoped state the caller depends on (a `DbContext`, response shaping) may be torn down mid-flight.\n\n**Fix direction:** change the return type to `Task` and `await` it from the calling action:\n\n```csharp\npublic async Task ProcessOrderAsync(Order order) { ... }\n// caller:\nawait ProcessOrderAsync(order);\n```\n\nThat single change restores exception propagation, makes completion observable, and keeps the call inside the request's scope lifetime.\n\n**Things I could not confirm without the full member and its call site, and that you should check directly:**\n\n- Whether `ProcessOrderAsync` awaits anything at all — if its body has no `await`, the `async` is spurious and the compiler warning (CS1998) already flags it; the method could simply be synchronous, or if it performs blocking I/O, that is a separate sync-over-async concern.\n- Whether the body captures request-scoped services (e.g. an injected `DbContext`) across an `await`. With `async void` there is no awaited boundary at the call site guaranteeing the scope outlives the work — verify the service lifetimes if the body outlives the immediate call.\n- Whether the method performs any `IDisposable`/`IAsyncDisposable` acquisition without `using`/`await using`; `async void` makes an exception-thrown-after-`await` path especially likely to skip cleanup. I cannot see the body here, so I am not asserting a leak — flagging it as something to inspect.\n- Whether the controller action is genuinely dispatching a fire-and-forget operation by design. If so, the correct shape is still `async Task` plus an explicit background-queue/`IHostedService` hand-off, not `async void`.\n\n**This is a diff-introduced issue** — the `async void` signature is new in this change, not pre-existing code merely touched.\n\nNo files were modified by this review.",
1252
+ "outputSha256": "02a9357c85b0d50489079826588c939f4c7df4c96d6321e0fd426f8b5b80e1b3",
1253
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1254
+ "deterministic": [],
1255
+ "judge": {
1256
+ "verdict": "pass",
1257
+ "reason": "Answer identifies `public async void ProcessOrderAsync` called from a regular controller action, not a UI/event handler (criterion 1). It states the concrete risk: exception after await bypasses the caller's try/catch and crashes the request/process (criterion 2). It gives fix direction — change return type to Task and await from caller — as a finding, and explicitly says 'No files were modified by this review' (criterion 3). No code change claimed."
1258
+ },
1259
+ "passed": true
1260
+ },
1261
+ {
1262
+ "output": "**Finding — `OrderController.ProcessOrderAsync`, new method, `async void` misuse**\n\n- **File:line** — `OrderController.cs`, on the newly-added `public async void ProcessOrderAsync(Order order)` member (exact line depends on the diff; report it as added at `OrderController.cs:<new-line>`).\n- **Pattern** — `async void` on a non-event-handler method. `ProcessOrderAsync` is an ordinary public method called from a regular controller action, not attached to a `+=` event-handler subscription. This matches the `async void` focus item from Step 2.\n- **Risk category** — async safety / unobserved exceptions. Two concrete consequences:\n 1. **Exceptions crash the process.** An exception escaping an `async void` method is not captured on a `Task`; it is raised on the `SynchronizationContext` (or thread pool) and, with no event-handler boundary to observe it, terminates the process rather than failing the request. The caller's `try`/`catch` around the call site cannot catch it — by the time the first `await` yields, the method has already returned `void` to the caller.\n 2. **Fire-and-forget semantics.** The calling controller action returns before `ProcessOrderAsync` completes. The response is sent, the request scope may dispose, and any scoped dependency (`DbContext`, etc.) the method holds can be torn down mid-flight — correct results are not guaranteed, and the caller cannot await completion to know whether the order actually processed.\n- **Diff-introduced vs. pre-existing** — this is **diff-introduced**: the method is new in this change, and the call from the regular controller action is new. Flag it as a new issue, not a pre-existing pattern in touched code.\n- **Fix direction (not applied)** — declare it `public async Task ProcessOrderAsync(Order order)` and `await` it from the calling action (making that action `async Task` as well). If the intent is genuinely fire-and-forget, return `Task` anyway and hand the work to a hosted/background queue (e.g. `IHostedService`/`Channel`) that owns its own scope — an `async void` controller method is never the right vehicle for that.\n\n**No other Step 2 categories found in the snippet as given.** The one-line description does not show a `.Result`/`.Wait()`, a `catch`, a nullable annotation, a `using`, or a DI capture, so I am not reporting those. If the full diff includes the method body or the call site with any of the following, they should be checked and reported separately:\n\n- a blocking `.Result`/`.Wait()`/`.GetAwaiter().GetResult()` added anywhere in this change,\n- a blanket `catch (Exception)` swallowing failures inside the new method,\n- a new unannotated nullable parameter/return later dereferenced, or a `!` used to silence a warning,\n- an `IDisposable` local created without `using`,\n- a scoped service captured into a singleton.\n\n**Confidence note** — the `async void` misuse and its exception-observation/return-before-completion consequences are certain from the declaration and call context alone. Whether the scope-teardown race actually manifests as a failure depends on what the method body awaits and what it touches; if you want that stated as a bug rather than a risk, reproduce under load / inspect the body to confirm the scoped-dependency usage.\n\n**Read-only** — no file was modified by this review; the fix direction above is a recommendation, not an edit.",
1263
+ "outputSha256": "f9bdb3e633fbe3ab5ff863166b69937b6981dd6b198aca503850792030a85fb8",
1264
+ "promptSha256": "28b18aba27498cbc995d1c59c8897a237755d57ff4745fa9eaf547f9f58aff20",
1265
+ "deterministic": [],
1266
+ "judge": {
1267
+ "verdict": "pass",
1268
+ "reason": "Answer identifies async void on ProcessOrderAsync as a non-event-handler method (\"ordinary public method called from a regular controller action, not attached to a += event-handler subscription\"). It states the concrete risk: exception escapes async void, not captured on a Task, terminates the process, caller's try/catch cannot catch it. Fix direction given: declare public async Task and await it from caller. Explicitly read-only: \"no file was modified... recommendation, not an edit.\" No code change claimed."
1269
+ },
1270
+ "passed": true
1271
+ }
1272
+ ]
1273
+ },
1274
+ {
1275
+ "id": "swallowed-exception-review",
1276
+ "kind": "behavior",
1277
+ "prompt": "Review this C# diff: a new block was added around a database call as `catch (Exception) { }` with no logging, no rethrow, and no narrower exception type. What's wrong here?",
1278
+ "strictness": "high",
1279
+ "trials": 10,
1280
+ "passes": 10,
1281
+ "passRate": 1,
1282
+ "passAtK": 1,
1283
+ "grader": "judge",
1284
+ "status": "ran",
1285
+ "trialRecords": [
1286
+ {
1287
+ "output": "The added `catch (Exception) { }` is a blanket, silently-swallowing handler — that's the core problem, and it combines three anti-patterns at once:\n\n**1. It catches far more than it can sensibly handle.**\n`catch (Exception)` catches *every* exception type a database call can throw — not just the \"expected business\" failures (e.g. a duplicate-key or concurrency violation) but also ones that indicate a real bug or outage: `OutOfMemoryException`, `StackOverflowException` (uncatchable, but the point stands), `SqlException` for a dropped connection or deadlock, a `TimeoutException`, a `NullReferenceException` from your own code inside the `try`. Narrowing to the specific type you actually anticipate (and letting everything else propagate) is the fix direction. A swallowed `SqlException` for a dead connection means the caller proceeds as if the write succeeded — data loss that surfaces much later and much harder to trace.\n\n**2. The empty body makes the failure silent.**\nNo log, no rethrow, no return-value signal. The method now returns normally on failure, so every upstream caller believes the database operation succeeded. This is worse than an unhandled exception, which at least fails loudly and leaves a stack trace. There is no record anywhere that the operation failed. At minimum, an anticipated exception should be logged (with the exception, not just a message) or rethrown wrapped; an unanticipated one should never be caught here at all.\n\n**3. It is placed around a database call specifically.**\nDB calls are exactly where you need the exception to survive, because the failure modes (timeout, deadlock, constraint violation, connection loss) are the ones you must decide about explicitly. An empty handler around one removes the only signal that distinguishes \"we wrote the row\" from \"we silently didn't.\"\n\n**Fix direction (not applied — this review is read-only):**\n- Catch the specific, expected exception type, e.g. `catch (DbUpdateException ex)` or `catch (SqlException ex) when (ex.Number == <specific>)` — not `Exception`.\n- In the handler, either log with the exception object (`_logger.LogError(ex, ...)`) and/or rethrow (bare `throw;`, never `throw ex;`), or translate to a domain-specific exception the caller can act on.\n- If the intended behavior is genuinely \"ignore this one known condition,\" a `when` filter that pins the exact condition plus a comment explaining *why* it is safe to ignore documents the intent; a bare `{ }` does not.\n\n**Classification:** this is a diff-introduced finding, not pre-existing — the empty catch is new in this block. It falls under the \"swallowed/blanket exception catches\" category.\n\nConfidence: the silent-swallow is certain from the diff as described. Whether it currently *causes* a data-loss incident depends on how callers use the return — reproduce with a forced DB failure (kill the connection mid-call) to confirm the operation now reports success.",
1288
+ "outputSha256": "a203d7b6222576a259d8d8548a1c28a0caa0d8e2e39a6c52357598c5bf9dffcb",
1289
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1290
+ "deterministic": [],
1291
+ "judge": {
1292
+ "verdict": "pass",
1293
+ "reason": "Answer identifies the blanket empty catch(Exception) as the core problem (point 1: 'blanket, silently-swallowing handler', 'No log, no rethrow, no return-value signal'). Point 2 gives concrete consequence: swallowed SqlException means 'caller proceeds as if the write succeeded — data loss', 'no record anywhere that the operation failed'. Point 3 names fix direction as a finding, explicitly 'not applied': catch specific type (DbUpdateException/SqlException with when filter), log with exception or rethrow. No 'just cleanup' excuse."
1294
+ },
1295
+ "passed": true
1296
+ },
1297
+ {
1298
+ "output": "The finding is a **blanket exception swallow**, and it's the exact pattern Step 2 (Exception handling) flags:\n\n**What's wrong**\n\n- **`catch (Exception)` is too broad.** It catches *everything* a call can throw, not just the failure the author actually anticipated. A database call can throw for many unrelated reasons — a `DbUpdateException`, a `TimeoutException`, a `InvalidOperationException` from a misconfigured connection, an `OperationCanceledException` from a cancelled request, even a `NullReferenceException` from a bug in the surrounding code. All of them get funneled into the same empty block, so a genuine bug looks identical to an expected one.\n- **An empty body swallows the failure silently.** No logging, no rethrow, no narrowing means that once this catch runs, there is *no evidence anywhere* that the database operation failed. The caller proceeds as if the call succeeded. Any downstream logic that depends on the write/read having happened now operates on a false premise — stale data, a \"success\" response for an operation that never completed, a retry that never fires.\n\n**Why it matters**\n\nThis is a silent-failure defect. It converts a loud, diagnosable crash into a quiet wrong-answer, which is strictly worse: a crash points at the line; a swallowed exception surfaces days later as a data-integrity bug with no stack trace to follow. It also defeats whatever error handling exists above it — a `try`/`catch` at the call site can never see the exception, cancellation can't propagate, and the process has no way to distinguish \"the DB call worked\" from \"the DB call blew up and I ignored it.\"\n\n**Fix direction (report only — do not apply)**\n\n- Catch the **specific** exception type the operation can actually throw and that the code knows how to handle (e.g. `catch (DbUpdateConcurrencyException ex)`), rather than `Exception`.\n- If the failure genuinely must be tolerated, make it **explicit and observable**: log it at warning/error level with the exception (and the operation context), and document *why* continuing is safe here.\n- Otherwise, **rethrow** — with `throw;` (not `throw ex;`, which resets the stack trace) or by letting it propagate untouched.\n- Never leave a catch block with an empty or no-op body.\n\n**Finding line**\n\n```\n<file>:<line> — catch (Exception) { } swallows the database call's failure\n with no logging, rethrow, or narrowing. Risk: silent failure — the caller\n proceeds as if the DB operation succeeded, and no stack trace survives for\n diagnosis. Fix direction: catch the specific expected exception type; if\n tolerating it is intentional, log the exception with context and document\n why continuation is safe; otherwise rethrow.\n```\n\nOne note on scoping per Step 3: if this `try`/`catch` wrapping is itself what the diff introduces, it's a diff-introduced finding. If the diff only touched code *inside* a pre-existing empty catch, report it as pre-existing but still worth fixing — either way it gets reported, just labeled correctly.",
1299
+ "outputSha256": "569df46e6a1daad91fa74d821f0f99b84c0c7bf92b1d7b340325c72de6e8a63e",
1300
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1301
+ "deterministic": [],
1302
+ "judge": {
1303
+ "verdict": "pass",
1304
+ "reason": "Criterion 1: answer names 'catch (Exception) is too broad' and 'empty body swallows the failure silently' with 'No logging, no rethrow, no narrowing.' Criterion 2: states caller 'proceeds as if the call succeeded' and 'no evidence anywhere' / 'no stack trace survives for diagnosis.' Criterion 3: fix direction given as 'report only — do not apply': catch specific type (DbUpdateConcurrencyException), log with context, or rethrow with 'throw;'. No fail criterion: it never excuses the empty catch as cleanup/best-effort."
1305
+ },
1306
+ "passed": true
1307
+ },
1308
+ {
1309
+ "output": "This is a blanket catch that swallows the exception — the highest-value finding in your diff. Breaking it down against the review focus list:\n\n**The pattern** — `catch (Exception) { }` at the call site around a database call: catches the base `Exception` type, empty body, no logging, no rethrow, no narrowing to the expected exception type.\n\n**Why it matters** — three distinct problems stack here:\n\n1. **Silent failure.** A DB call that throws (timeout, connection failure, constraint violation, `DbUpdateException`) fails silently. The method returns as if the call succeeded. Callers get no signal that the write/read didn't happen. This is the classic \"catch (Exception ex) { } with an empty or no-op body\" flag.\n2. **Over-broad type.** `catch (Exception)` also catches `OutOfMemoryException`, `StackOverflowException` (though that one is unrecoverable anyway), `OperationCanceledException` for a cancellation that means the request is going away, and programming errors like `NullReferenceException`/`InvalidOperationException` from a bug in the surrounding code. All of those are now invisible too, not just the DB failure you presumably meant to handle.\n3. **No exception contract.** Whatever the block was added to \"handle,\" the caller's own `try`/`catch` (if any) never sees anything, so the failure never surfaces anywhere in the stack.\n\n**Fix direction** (not applied — this review is read-only):\n- Catch the specific exception type the DB call can actually throw — typically `DbUpdateException` for EF Core writes, or a provider-specific exception like `SqlException`, not `Exception`.\n- If the intent was to tolerate the failure, log it at minimum (`ILogger<...>.LogError(ex, ...)`) and decide deliberately whether to swallow or rethrow; a silent swallow is never the right default.\n- If a genuinely broad catch is needed as a last-resort boundary (e.g. a top-level handler for one worker loop), it should log and rethrow — not swallow.\n\n**One thing to confirm before you report it as certain:** whether the surrounding method itself is the intended error boundary. If this block is the top of a background processing loop that logs failures at an outer level, then the \"silent\" part may be covered and only the over-broad catch type remains a finding. Read the enclosing method and its caller to be sure — otherwise report as diff-introduced with the standard silent-failure rationale.\n\nFormatted as it would appear in the review:\n\n```\n<file>:<line> — catch (Exception) { } around the DB call: swallows every\n exception type with no log/rethrow/narrowing. Risk: DB failures fail\n silently; programming errors (NRE/InvalidOperation) also masked; caller\n never sees the failure. Fix direction: catch the specific type the call\n throws (DbUpdateException/SqlException), log it, and decide\n swallow-vs-rethrow explicitly rather than defaulting to empty.\n```\n\nNo file was modified. If this same empty-catch pattern exists elsewhere in the touched file but predates this diff, report it separately as pre-existing rather than attributing it to this change.",
1310
+ "outputSha256": "3626babb5dd7383d99b17c24ec8c688cb7ebc3f8517ec96ae9d77bf56c940571",
1311
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1312
+ "deterministic": [],
1313
+ "judge": {
1314
+ "verdict": "pass",
1315
+ "reason": "Answer identifies the empty blanket catch(Exception) with no log/rethrow/narrowing (\"The pattern\" section), states concrete consequences — DB failures (timeout, connection failure, constraint violation, DbUpdateException) fail silently, caller gets no signal, programming errors masked (points 1-3), and gives fix direction as a finding not applied: catch DbUpdateException/SqlException, log via ILogger, decide swallow-vs-rethrow. No 'just cleanup' excuse."
1316
+ },
1317
+ "passed": true
1318
+ },
1319
+ {
1320
+ "output": "The problem is a **blanket, silent swallow**: `catch (Exception) { }` catches *every* exception type — including ones this code cannot actually handle, like `OutOfMemoryException`, `OperationCanceledException` from a cancelled request, or a programming-error `NullReferenceException` — and then discards it with no log, no rethrow, and no narrowed type. The database call's success is now indistinguishable from its failure to any caller or operator.\n\n**Why it matters concretely**\n\n- **Silent failure.** If the DB call throws (connection failure, deadlock, constraint violation, timeout), execution now continues past the `catch` as if the operation succeeded. The caller gets no signal, so downstream logic — and any user-visible outcome — proceeds on a false assumption.\n- **Unhandleable exceptions get handled anyway.** The block swallows exception types it has no business absorbing. A genuine bug or infrastructure failure is now hidden instead of surfacing in logs, crash reporting, or an error response.\n- **Debuggability loss.** No logging means the only artifact of the failure is the *absence* of an expected effect — typically discovered much later, far from the cause. This is the worst kind of production bug to trace.\n\n**Fix direction (for the author, not applied here — this review is read-only)**\n\n- Narrow the catch to the specific exception type(s) the DB call is actually expected to throw — e.g. `catch (DbUpdateException ex)` or `catch (SqlException ex)` when a retry, fallback, or \"already exists\" outcome is intended — and let anything else propagate.\n- If the code genuinely must handle a broad category, at minimum **log it** (with the exception and enough context to identify the operation) and either **rethrow** with `throw;` (preserving the stack trace — never `throw ex;`) or explicitly convert it to a well-defined result the caller checks.\n- An empty catch body is a red flag on its own; a `catch` should have a reason to exist. If there is no handling behavior, the `catch` should not be there at all.\n\n**One caveat before asserting the full impact:** whether this currently *masks* a real failure or merely obscures a path that is never actually exercised depends on how the DB call can fail in this code path. From the diff alone I can flag the pattern and its risk with certainty; confirming the severity under real conditions would mean watching the failure path actually run — e.g. reproduce by forcing the DB call to throw and confirm the failure is observed (logged, surfaced, or retried) rather than vanishing. As written, it does not.\n\nFinding: `catch (Exception) { }` around the new database call — file:line as marked in the diff — silent exception swallow, no logging/rethrow/narrowing. Fix direction: catch the specific expected exception type, and log or rethrow as above.",
1321
+ "outputSha256": "b55a585f67f7c845326c0ca79ec086dbfb88b8ee4d01bcdec5a36d745a6f3794",
1322
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1323
+ "deterministic": [],
1324
+ "judge": {
1325
+ "verdict": "pass",
1326
+ "reason": "Answer identifies the blanket silent swallow with no log/rethrow/narrowed type (\"catches every exception type... discards it with no log, no rethrow, and no narrowed type\"). Consequence stated: DB failures (connection failure, deadlock, constraint violation, timeout) hidden, caller gets no signal, no logs. Fix direction given as finding, not applied: narrow to DbUpdateException/SqlException, log and rethrow with throw;. No 'just cleanup' excuse."
1327
+ },
1328
+ "passed": true
1329
+ },
1330
+ {
1331
+ "output": "That's a blanket exception swallow — the diff catches the most general type possible and discards it silently, so any failure from the database call (a `DbUpdateException`, a transient `SqlException` worth retrying, a `TaskCanceledException` from a caller-side cancellation, or a genuine `NullReferenceException` from a bug in the code being called) disappears with no trace.\n\nSpecifics worth naming in the review:\n\n- **`catch (Exception)` is broader than the diff can justify.** The catch should name the specific exception type the call is actually expected to throw in a recoverable case. Catching everything means unrelated failure modes — programmer errors included — are silently treated as the same \"expected\" condition.\n- **Empty body = no logging, no rethrow, no fallback.** Nothing downstream (caller, logs, telemetry, metrics) can tell the operation failed. The caller proceeds as if the DB call succeeded. That is the silent-failure risk this pattern creates: a corrupted write, a dropped row, or an unimplemented state masquerading as success.\n- **No rethrow means the stack trace is lost.** If anything does need to know, there's nothing to correlate against.\n- **If the intent really was \"the DB is allowed to be down, continue anyway,\"** that intent needs to be explicit — catch the specific transient exception, log it at minimum at debug/info, and either retry or return a documented fallback value. \"Continue\" is a decision, not an absence.\n\nFix direction (not to be applied by this review — findings only):\n\n1. Narrow to the specific exception type the call is expected to throw (e.g. `DbUpdateConcurrencyException`, or a specific transient SQL error), and let everything else propagate.\n2. If swallowing is genuinely correct for that narrow type, log it (`ILogger.LogWarning`/`LogError`) with the exception, and either retry or return a clearly-defined fallback.\n3. If the catch is only there to satisfy a \"never throw from this path\" contract, make that contract visible in the method's name or an XML doc comment, and still log.\n\nIf the reviewer isn't certain which exception type the call can actually throw at runtime, that's a signal to check the provider's documented exceptions or reproduce with a forced failure — not to widen the catch to `Exception` and call it handled.",
1332
+ "outputSha256": "2da2911eb02923a43bff20255c1bc7864fe5c0ed21331800fe5e2b57c95a0732",
1333
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1334
+ "deterministic": [],
1335
+ "judge": {
1336
+ "verdict": "pass",
1337
+ "reason": "Answer identifies the blanket catch(Exception) with empty body as silently discarding failures (\"blanket exception swallow... discards it silently\"), names concrete hidden failures (DbUpdateException, SqlException, NullReferenceException) with no trace/log/rethrow, states caller proceeds as if DB succeeded with no signal to caller/logs/telemetry, and gives fix direction as findings only: narrow to specific exception type, log with ILogger, retry/fallback. No 'just cleanup' excuse."
1338
+ },
1339
+ "passed": true
1340
+ },
1341
+ {
1342
+ "output": "This is a banned blanket catch: `catch (Exception) { }` with an empty body swallows every failure — including ones the code never anticipated — and hides it completely.\n\nTwo distinct problems, both from the focus list:\n\n1. **Blanket `catch (Exception)`** — the handler is wider than any real expectation. The diff should catch the specific exception type the database call can actually throw (e.g. `DbUpdateConcurrencyException`, `SqlException`, a specific application exception), not `Exception`.\n2. **Silent failure** — an empty body means the exception vanishes: no log, no rethrow, no fallback action. A failed DB call becomes indistinguishable from a successful one to every caller up the stack. This is a correctness bug, not just a style nit.\n\n**Fix direction:** narrow the catch to the exception type actually expected, and either log-and-handle it explicitly or let unexpected types propagate. If the intent was genuinely \"this call is optional\", the code must still record that the call failed — not discard it.\n\nPer this skill I'm reporting only; no edit is made here.\n\n**Finding:**\n\n```\nsrc/<path>/<file>.cs:<line> — catch (Exception) { } around the DB call.\n Risk: every exception from the DB call, expected or not, is swallowed\n silently; callers cannot tell success from failure. Fix direction:\n catch the specific exception type the call throws, log or handle it\n explicitly, and let unexpected exceptions propagate.\n```\n\nOne caution: I don't have the diff or the file open in this exercise, so line numbers and types are placeholders — confirm the actual file:line and the concrete exception type before filing.",
1343
+ "outputSha256": "608433288e227b6eccb759250217ef41bc1c7f46ca994014299f92db67891ca6",
1344
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1345
+ "deterministic": [],
1346
+ "judge": {
1347
+ "verdict": "pass",
1348
+ "reason": "Answer identifies the empty blanket catch(Exception){} as swallowing every failure with no log/rethrow/narrower type (points 1-2), states the consequence that a failed DB call is indistinguishable from success to callers (point 2), and gives fix direction: narrow to specific exception type (DbUpdateConcurrencyException/SqlException) and log-and-handle or let unexpected propagate, reported as a finding with no edit made (point 3). No 'just cleanup' excuse."
1349
+ },
1350
+ "passed": true
1351
+ },
1352
+ {
1353
+ "output": "This is a **blanket, silent exception swallow** — two distinct problems stacked in one catch block:\n\n**1. It catches too much.** `catch (Exception)` intercepts *every* exception type thrown by the guarded database call, not just the one the code plausibly expects. A real database call can throw:\n- `DbUpdateException` / `SqlException` for a constraint violation, deadlock, or connection failure\n- `OperationCanceledException` when a request is aborted or a timeout fires\n- `InvalidOperationException` for a genuine programming bug (e.g. a disposed `DbContext`, a misconfigured entity)\n- `ObjectDisposedException`, `TimeoutException`, etc.\n\nBy catching `Exception`, the handler treats a transient, retryable connectivity blip and a hard coding defect identically — both vanish.\n\n**2. It swallows silently.** The body is `{ }`: no logging, no rethrow, no telemetry, no return value indicating failure. So the exception is gone the instant it's caught. Everything downstream continues as if the database call succeeded.\n\n## Why it matters\n\nThe practical effect is a **silent data-integrity / correctness failure**, not just a cosmetic logging gap:\n\n- **The caller believes the write happened.** If the exception was a failed `SaveChanges` (constraint violation, deadlock victim, connection dropped mid-transaction), execution proceeds with in-memory state that disagrees with the database. Nothing surfaces the divergence.\n- **The failure is undiagnosable in production.** No log line, no exception detail, no stack trace. When a human later notices \"these rows are missing,\" there is zero evidence tracing it to this point — the only artifact was a thrown exception that this block erased.\n- **A real bug is masked.** If the exception was `InvalidOperationException` from a disposed `DbContext` or a bad query, the bug now reproduces as *missing data* instead of a clear crash, which is far more expensive to find.\n- **Cancellation is destroyed.** If it was `OperationCanceledException`, swallowing it defeats request-abort/shutdown semantics.\n\nThis is the classic red-flag rationalization \"the blanket catch is fine, it's just cleanup/defensive code.\" It is not fine: cleanup that fails silently still hides a real bug.\n\n## Fix direction (report only — this review is read-only)\n\n1. **Narrow the type.** Catch only the specific exception the call can actually throw and that the code has a genuine recovery for (e.g. `DbUpdateException` for an expected unique-constraint race) — or let an unexpected one propagate.\n2. **Never leave it empty.** If the catch is truly warranted, either:\n - **log** with enough context to diagnose (`ILogger` with the exception and the operation/keys involved), or\n - **rethrow** (`throw;`, not `throw ex;`) after any needed side effect, or\n - return a **failure result** the caller is forced to handle — never a silent success.\n3. If the intent was \"best-effort, don't fail the request,\" say so explicitly and make it deliberate: narrow the type, log at Warning, and document *why* swallowing is safe here.\n\nSuggested finding format:\n\n```\nsrc/Data/OrderRepository.cs:42 — catch (Exception) { } with an empty body\n around the DB call. Risk: all exceptions (transient connection failure,\n constraint violation, disposed-context bug, cancellation) are silently\n discarded; the caller proceeds as if the write succeeded, producing\n undiagnosable data divergence. Fix direction: catch the specific expected\n exception type only, and either log it or rethrow — never swallow.\n```\n\n**Verification note:** whether a specific swallowed exception here caused a *concrete* observed data loss cannot be asserted from reading the diff alone — the empty body guarantees the failure is unlogged, so confirm impact by reproducing the failure path (or via a resource/trace profiler), not by reading the code in isolation.",
1354
+ "outputSha256": "e2e49b5f14be1d666a511cd71ec03d8c56a8b689c267bc232f99ebb73724ce13",
1355
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1356
+ "deterministic": [],
1357
+ "judge": {
1358
+ "verdict": "pass",
1359
+ "reason": "Criterion 1: answer calls it a 'blanket, silent exception swallow' with body '{ }': no logging, no rethrow, no narrower type. Criterion 2: states caller 'believes the write happened', failure 'undiagnosable in production', no log/stack trace. Criterion 3: fix direction given as report-only ('this review is read-only'): narrow type, log or rethrow. No fail criterion: explicitly rejects 'just cleanup' excuse."
1360
+ },
1361
+ "passed": true
1362
+ },
1363
+ {
1364
+ "output": "The added `catch (Exception) { }` block is a swallowed blanket catch — the change catches every exception type the database call could throw, then discards it with no logging, no rethrow, and no narrowing. That's a silent-failure bug, not just a style nit: when the database call fails for any reason (connection loss, timeout, constraint violation, a query bug), execution continues past the block as if nothing happened, and all evidence of why it failed is gone. The caller sees a \"successful\" code path with no data, no error signal, and no trace to debug from.\n\nSpecific problems:\n\n1. **Blanket `catch (Exception)` hides programming errors.** It does not just swallow transient database faults — it also swallows `NullReferenceException`, `InvalidOperationException`, argument miscalculation, and anything else a bug would surface as. Those should propagate to fail loudly, not be silently absorbed.\n\n2. **Empty body = no observability.** No `ILogger` call, no metrics, no rethrow. In production this turns a diagnosable failure into \"it just didn't work sometimes.\"\n\n3. **The catch likely masks a control-flow question.** If the database call is expected to fail in some known, handleable way, the code should catch that specific exception type (e.g. the provider's transient/concurrency exception) and do something deliberate — retry, fall back, return a typed failure result. Catching `Exception` with an empty body is not handling; it's erasing.\n\nFix direction (not applied — this review is read-only):\n\n- Narrow the catch to the specific exception type actually expected, and handle it explicitly: log with context, rethrow if it's not the expected case, or return a documented fallback/`Result`.\n- If the intent is genuinely \"best-effort, never fail the request,\" make that explicit: `catch (SpecificDbException ex) { _logger.LogWarning(ex, \"Non-fatal: <what> failed for <id>\"); }` — log at minimum, and keep the catch as narrow as the contract allows.\n- If neither applies, remove the try/catch and let the exception propagate.\n\nVerify this is diff-introduced rather than pre-existing: if the `catch (Exception) { }` already existed in the touched method and the diff only moved/re-added the database call inside it, report it as a pre-existing issue the diff now routes failures into, separately from a diff-introduced one.\n\nRelated flags to check in the same change, since they usually travel together with this pattern: whether the swallowed failure leaves a resource undisposed (missing `using` on the DB command/connection/reader on the failure path), and whether the surrounding method is `async void` — a swallowed exception inside `async void` is doubly bad, since the crash-vs-silence behavior becomes unpredictable to the caller.",
1365
+ "outputSha256": "4c2c3fe8e2d8e394b37ce24541c50d1890bad83dfeabe5dedffc4339d7174116",
1366
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1367
+ "deterministic": [],
1368
+ "judge": {
1369
+ "verdict": "pass",
1370
+ "reason": "Answer identifies the empty blanket catch(Exception) swallowing failures with no log/rethrow/narrowing (\"swallowed blanket catch... no logging, no rethrow, and no narrowing\"). States concrete consequence: real DB failures (connection loss, timeout, constraint violation) hidden, caller sees success with no error signal/trace. Names fix direction as finding, not applied: narrow to specific exception type, log/rethrow, or remove try/catch. Does not excuse it as best-effort."
1371
+ },
1372
+ "passed": true
1373
+ },
1374
+ {
1375
+ "output": "The diff adds a blanket exception swallow around the database call: `catch (Exception) { }` with no logging, no rethrow, and no narrowing to the exception type the call can actually throw. That is a silent-failure defect, not a stylistic nit.\n\nWhat's wrong specifically:\n\n1. **It catches far more than the call's real failure modes.** A database call fails in a handful of ways you actually anticipate — a duplicate-key/constraint violation (`DbUpdateException` with a unique-index inner exception), a concurrency conflict (`DbUpdateConcurrencyException`), a not-found row, a timeout/transient fault (`DbException`/`SqlException` with a transient error number), a cancellation (`OperationCanceledException`). Catching `Exception` also captures bugs you have no business suppressing: a `NullReferenceException` from your own mapping code, an `InvalidOperationException` from a misconfigured model, an `ObjectDisposedException` from a lifetime bug, an `OutOfMemoryException`. Those should crash loudly or bubble to your global handler, not disappear.\n\n2. **The empty body destroys all evidence.** No log, no metric, no rethrow. When this path starts firing in production, there is no trace it happened, no error rate to alert on, no stack trace to debug from. The caller receives a \"successful\" path with the operation silently not performed — the worst outcome for correctness, because downstream code now proceeds as if the write/read succeeded.\n\n3. **It obscures whether the failure is recoverable.** Some of these exceptions are retryable (transient SQL fault, deadlock victim), some are not (constraint violation), and some mean the caller needs to know (concurrency conflict → reload and retry, or surface to the user). A single empty catch collapses all three into \"pretend nothing happened,\" so no caller can make the right decision.\n\nFix direction (I'm not applying it — this is a read-only review):\n\n- Catch the **specific** exception type(s) the call can throw, not `Exception`.\n- If the intent is genuinely \"ignore a known-benign condition\" (e.g., a duplicate insert that is fine to skip), catch exactly that type and **log at least** `Debug`/`Information` so the occurrence is observable.\n- If the intent is \"keep going regardless,\" that is almost never correct around a DB call — either let it propagate to a handler that decides policy, or catch narrowly, log with the exception, and return a meaningful failure result (a `Result`/error, not silent success).\n- For transient faults, retry deliberately (e.g., via a retry policy) rather than swallowing.\n- Always pass the exception to the logger (`_logger.LogError(ex, ...)`) — never log a bare message, or you lose the stack.\n\nAlso flag in the report whether this `catch` is **new in this diff** or **pre-existing** in code the diff merely touched — that changes who should fix it. If you can characterize which explicit exceptions the surrounding call can raise, name them in the finding; if not, state that the set should be derived from the call site rather than guessed, and that the swallowed-failure behavior should be confirmed with a test that asserts an exception actually escapes or is logged rather than vanishing.",
1376
+ "outputSha256": "d4907ba159617350fe3fbef40c10115c66ba156a93fe24b51f45691eab22d589",
1377
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1378
+ "deterministic": [],
1379
+ "judge": {
1380
+ "verdict": "pass",
1381
+ "reason": "Answer identifies the blanket `catch (Exception) { }` with no log/rethrow/narrowing (point 1), states the concrete consequence: no log/metric/rethrow, caller gets 'successful' path with operation silently not performed, no trace/alert/stack trace (point 2), and gives fix direction as a finding, explicitly 'I'm not applying it — this is a read-only review': catch specific type, log at least Debug/Information, log with exception, retry transient faults (point 3). No 'just cleanup/best-effort' excuse."
1382
+ },
1383
+ "passed": true
1384
+ },
1385
+ {
1386
+ "output": "`catch (Exception) { }` — an empty, type-agnostic catch — is a silent-failure finding, and it's one of the clearest ones to flag.\n\n**What's wrong**\n\n- **It swallows everything.** `catch (Exception)` catches every exception type the `try` block can throw, including ones the caller almost certainly needs to see (connection failures, command timeouts, constraint violations). An empty body means none of them are surfaced — no rethrow, no log, no metric, no narrowing.\n- **The failure disappears.** After the catch, execution continues as if the database call succeeded. Any code downstream that assumes the call worked now operates on stale or absent data, and the eventual symptom surfaces far from the cause — a much harder bug to trace than the original exception would have been.\n- **It's too broad to be intentional.** The set of exceptions that are safe to ignore at a given call site is almost always a small, specific set (`DbUpdateConcurrencyException` you're explicitly retrying past, a duplicate-key violation you're deliberately tolerating, etc.). `catch (Exception)` is the opposite of that — it can't distinguish \"expected and handled\" from \"the process is on fire.\"\n\n**What the diff should do instead**\n\n- Catch the specific exception type(s) this call site actually expects and can recover from; let anything unexpected propagate.\n- If the catch is intentional, give it a body that does something observable: log with enough context (the operation, key args, correlation id) and either rethrow or return a well-defined failure result. A catch that does neither is indistinguishable from deleting the error.\n- If the intent was \"don't fail the outer operation on a best-effort side effect,\" say so explicitly — narrow the type and log at `Warning`/`Debug` with the reason, rather than a blank `catch (Exception) { }`.\n\n**Report it as**\n\n```\n<file>:<line> — catch (Exception) { } around the database call swallows\n every exception type with no logging, no rethrow, and no narrowing.\n Risk: failed writes/reads proceed silently; downstream code consumes\n stale data and the real error is lost, surfacing far from its cause.\n Fix direction: catch the specific exception(s) expected here (or none),\n and log-and-rethrow / return a failure result for what remains.\n```\n\nTwo things I can't confirm without the actual diff and surrounding code, so I'm stating them as questions rather than assertions:\n\n- Whether this `try`/`catch` is new in the diff or pre-existing code the diff merely wrapped. If the block is newly added around an existing call, it's a diff-introduced finding; if the catch already existed and the diff just moved code into it, note it as pre-existing.\n- Whether there's an outer handler that already logs. Even if one exists, an empty inner catch still discards the exception's origin and stack — but it changes the severity language, so it's worth checking before writing up.\n\nNo code was edited in producing this review.",
1387
+ "outputSha256": "76f8dbdefb43d3a14ae1845ca51578d6feb3c41de0f3300eff43da8bd672fc6e",
1388
+ "promptSha256": "9703ae3696561df6bfee768627ceb0fcb62144a7592e014ececaf6ea2bd20f41",
1389
+ "deterministic": [],
1390
+ "judge": {
1391
+ "verdict": "pass",
1392
+ "reason": "Answer identifies the empty catch(Exception) as swallowing all exception types with 'no rethrow, no log, no metric, no narrowing' (crit 1). States concrete consequence: 'The failure disappears... execution continues as if the database call succeeded... real error is lost' with no signal to caller/ops (crit 2). Names fix direction as a finding: 'Catch the specific exception type(s)... log with enough context and either rethrow or return a well-defined failure result' (crit 3), and explicitly says 'No code was edited.' No fail criterion: it rejects the best-effort excuse, requiring narrowing and logging."
1393
+ },
1394
+ "passed": true
1395
+ }
1396
+ ]
1397
+ }
1398
+ ],
1399
+ "verdict": "fail",
1400
+ "scope": "bundled",
1401
+ "skillDigest": "ac06e4c511d72ef27cc1034f66826dffedc1c6e6a796d50b0a3b09180851c7d5",
1402
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
1403
+ "judgePromptVersion": "2026-09-25.1",
1404
+ "runner": "deepseek",
1405
+ "model": "deepseek-chat",
1406
+ "runnerPromptVersion": "2026-09-25.1",
1407
+ "recordedAt": "2026-09-25T18:15:00.945Z",
1408
+ "judge": "deepseek",
1409
+ "judgeModel": "deepseek-chat"
1410
+ },
1411
+ {
1412
+ "schemaVersion": "1.0.0",
1413
+ "skillId": "csharp-dotnet/dotnet-build-fix",
1414
+ "strictness": "high",
1415
+ "trials": 10,
1416
+ "triggerAccuracy": {
1417
+ "truePositive": 1,
1418
+ "falsePositive": 0,
1419
+ "positives": 7,
1420
+ "negatives": 8
1421
+ },
1422
+ "evidence": "authored",
1423
+ "scenarios": [
1424
+ {
1425
+ "id": "trigger-positive-1",
1426
+ "kind": "trigger-positive",
1427
+ "prompt": "The compiler is complaining about a possible null reference here, fix it",
1428
+ "strictness": "high",
1429
+ "trials": 1,
1430
+ "passes": 1,
1431
+ "passRate": 1,
1432
+ "passAtK": 1,
1433
+ "grader": "trigger-rank-fork-family",
1434
+ "status": "ran",
1435
+ "deterministic": true
1436
+ },
1437
+ {
1438
+ "id": "trigger-positive-2",
1439
+ "kind": "trigger-positive",
1440
+ "prompt": "This NuGet package version conflict is blocking my build, resolve it",
1441
+ "strictness": "high",
1442
+ "trials": 1,
1443
+ "passes": 0,
1444
+ "passRate": 0,
1445
+ "passAtK": 0,
1446
+ "grader": "trigger-rank-fork-family",
1447
+ "status": "ran",
1448
+ "deterministic": true
1449
+ },
1450
+ {
1451
+ "id": "trigger-positive-3",
1452
+ "kind": "trigger-positive",
1453
+ "prompt": "An analyzer rule is failing on this awaited call in library code",
1454
+ "strictness": "high",
1455
+ "trials": 1,
1456
+ "passes": 0,
1457
+ "passRate": 0,
1458
+ "passAtK": 0,
1459
+ "grader": "trigger-rank-fork-family",
1460
+ "status": "ran",
1461
+ "deterministic": true
1462
+ },
1463
+ {
1464
+ "id": "trigger-positive-4",
1465
+ "kind": "trigger-positive",
1466
+ "prompt": "dotnet test just started failing after I touched this class",
1467
+ "strictness": "high",
1468
+ "trials": 1,
1469
+ "passes": 0,
1470
+ "passRate": 0,
1471
+ "passAtK": 0,
1472
+ "grader": "trigger-rank-fork-family",
1473
+ "status": "ran",
1474
+ "deterministic": true
1475
+ },
1476
+ {
1477
+ "id": "trigger-positive-5",
1478
+ "kind": "trigger-positive",
1479
+ "prompt": "My project won't restore anymore, something's wrong with the package graph",
1480
+ "strictness": "high",
1481
+ "trials": 1,
1482
+ "passes": 0,
1483
+ "passRate": 0,
1484
+ "passAtK": 0,
1485
+ "grader": "trigger-rank-fork-family",
1486
+ "status": "ran",
1487
+ "deterministic": true
1488
+ },
1489
+ {
1490
+ "id": "trigger-positive-6",
1491
+ "kind": "trigger-positive",
1492
+ "prompt": "This CS error is blocking the build, what's the actual fix?",
1493
+ "strictness": "high",
1494
+ "trials": 1,
1495
+ "passes": 0,
1496
+ "passRate": 0,
1497
+ "passAtK": 0,
1498
+ "grader": "trigger-rank-fork-family",
1499
+ "status": "ran",
1500
+ "deterministic": true
1501
+ },
1502
+ {
1503
+ "id": "trigger-positive-7",
1504
+ "kind": "trigger-positive",
1505
+ "prompt": "Sort out this StyleCop violation without just turning the rule off",
1506
+ "strictness": "high",
1507
+ "trials": 1,
1508
+ "passes": 0,
1509
+ "passRate": 0,
1510
+ "passAtK": 0,
1511
+ "grader": "trigger-rank-fork-family",
1512
+ "status": "ran",
1513
+ "deterministic": true
1514
+ },
1515
+ {
1516
+ "id": "trigger-negative-1",
1517
+ "kind": "trigger-negative",
1518
+ "prompt": "npm install is failing with a peer dependency conflict",
1519
+ "strictness": "high",
1520
+ "trials": 1,
1521
+ "passes": 1,
1522
+ "passRate": 1,
1523
+ "passAtK": 1,
1524
+ "grader": "trigger-rank-fork-family",
1525
+ "status": "ran",
1526
+ "deterministic": true
1527
+ },
1528
+ {
1529
+ "id": "trigger-negative-2",
1530
+ "kind": "trigger-negative",
1531
+ "prompt": "cargo build is failing for this Rust crate",
1532
+ "strictness": "high",
1533
+ "trials": 1,
1534
+ "passes": 1,
1535
+ "passRate": 1,
1536
+ "passAtK": 1,
1537
+ "grader": "trigger-rank-fork-family",
1538
+ "status": "ran",
1539
+ "deterministic": true
1540
+ },
1541
+ {
1542
+ "id": "trigger-negative-3",
1543
+ "kind": "trigger-negative",
1544
+ "prompt": "pip install is failing for this Python project",
1545
+ "strictness": "high",
1546
+ "trials": 1,
1547
+ "passes": 1,
1548
+ "passRate": 1,
1549
+ "passAtK": 1,
1550
+ "grader": "trigger-rank-fork-family",
1551
+ "status": "ran",
1552
+ "deterministic": true
1553
+ },
1554
+ {
1555
+ "id": "trigger-negative-4",
1556
+ "kind": "trigger-negative",
1557
+ "prompt": "This Xcode build is failing with a Swift compiler error",
1558
+ "strictness": "high",
1559
+ "trials": 1,
1560
+ "passes": 1,
1561
+ "passRate": 1,
1562
+ "passAtK": 1,
1563
+ "grader": "trigger-rank-fork-family",
1564
+ "status": "ran",
1565
+ "deterministic": true
1566
+ },
1567
+ {
1568
+ "id": "trigger-negative-5",
1569
+ "kind": "trigger-negative",
1570
+ "prompt": "Gradle sync is failing for this Kotlin Android project",
1571
+ "strictness": "high",
1572
+ "trials": 1,
1573
+ "passes": 1,
1574
+ "passRate": 1,
1575
+ "passAtK": 1,
1576
+ "grader": "trigger-rank-fork-family",
1577
+ "status": "ran",
1578
+ "deterministic": true
1579
+ },
1580
+ {
1581
+ "id": "trigger-negative-6",
1582
+ "kind": "trigger-negative",
1583
+ "prompt": "flutter pub get is failing with a version solving conflict",
1584
+ "strictness": "high",
1585
+ "trials": 1,
1586
+ "passes": 1,
1587
+ "passRate": 1,
1588
+ "passAtK": 1,
1589
+ "grader": "trigger-rank-fork-family",
1590
+ "status": "ran",
1591
+ "deterministic": true
1592
+ },
1593
+ {
1594
+ "id": "trigger-negative-7",
1595
+ "kind": "trigger-negative",
1596
+ "prompt": "Implement a new C# feature in the order service",
1597
+ "strictness": "high",
1598
+ "trials": 1,
1599
+ "passes": 1,
1600
+ "passRate": 1,
1601
+ "passAtK": 1,
1602
+ "grader": "trigger-rank-fork-family",
1603
+ "status": "ran",
1604
+ "deterministic": true
1605
+ },
1606
+ {
1607
+ "id": "trigger-negative-8",
1608
+ "kind": "trigger-negative",
1609
+ "prompt": "Review this C# diff for async void misuse",
1610
+ "strictness": "high",
1611
+ "trials": 1,
1612
+ "passes": 1,
1613
+ "passRate": 1,
1614
+ "passAtK": 1,
1615
+ "grader": "trigger-rank-fork-family",
1616
+ "status": "ran",
1617
+ "deterministic": true
1618
+ },
1619
+ {
1620
+ "id": "nullable-warning-root-cause",
1621
+ "kind": "behavior",
1622
+ "prompt": "The compiler reports CS8602 (possible null reference) on the line right after `orders.TryGetValue(orderId, out var order);` where I then call `order.Total`. How do I fix it correctly?",
1623
+ "strictness": "high",
1624
+ "trials": 10,
1625
+ "passes": 9,
1626
+ "passRate": 0.9,
1627
+ "passAtK": 1,
1628
+ "grader": "judge",
1629
+ "status": "ran",
1630
+ "trialRecords": [
1631
+ {
1632
+ "output": "`Dictionary<TKey, TValue>.TryGetValue` has this signature:\n\n```csharp\npublic bool TryGetValue(TKey key, out TValue value);\n```\n\nThe nullable-flow-analysis problem is that when `TValue` is a reference type, the compiler cannot prove `value` is assigned a non-null value on the `false` path — so after the call, `order` has a \"maybe null\" state, and only *inside* an `if (orders.TryGetValue(...))` block does the true-branch tell the compiler the `out` value is the found, non-null entry. Reading `order.Total` immediately afterward (without branching) is exactly what triggers CS8602.\n\nHere is the correct fix — branch on the return value and do the work inside the true branch:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n // here `order` is flow-analyzed as non-null\n Console.WriteLine(order.Total);\n}\nelse\n{\n // not found -- handle it explicitly (throw, return, default, etc.)\n}\n```\n\n**Why this is the right fix and not a suppression:**\n\n- It fixes the actual null path: the code now only dereferences `order` on the path where `TryGetValue` returned `true`, which is exactly the invariant the API guarantees.\n- Do **not** resolve this with the null-forgiving operator (`order!.Total`). That tells the compiler to trust you without establishing any invariant, and it will throw `NullReferenceException` at runtime on the not-found path — the same bug the warning was pointing at.\n- Do **not** wrap the block in `#pragma warning disable CS8602`, and do **not** add `<Nullable>disable</Nullable>`. Both hide the real gap instead of closing it.\n\n**If there is genuinely no sensible order (e.g. the caller guarantees it exists), make that guarantee real rather than assumed:**\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n throw new KeyNotFoundException($\"No order for id {orderId}.\");\n\n// `order` is non-null past the guard\nConsole.WriteLine(order.Total);\n```\n\nThis is the same shape as the `if/else` above but expresses the \"must exist\" contract as an enforced precondition instead of an unchecked assumption.\n\n**Two related cases worth noting:**\n\n1. If `order.Total` itself is what's dereferenced and the real question is whether `order` is null, the guard above is the fix. If instead the warning is on a *member* of `order` being null (a decimal `Total?`), the guard is still needed *and* that member needs its own null handling — but the CS8602 line after `TryGetValue` is about `order` itself.\n2. If you need the value and don't care about the boolean (e.g. indexer semantics), `orders[orderId]` throws `KeyNotFoundException` on miss and returns a non-null reference, so `orders[orderId].Total` compiles cleanly — but only use that form when a missing key should genuinely be exceptional, since it changes behavior from \"return false\" to \"throw.\"\n\nEither way, the deciding question is: *what should happen when the order is not found?* Answer that explicitly in code, and the warning goes away because the null path is actually handled — which is the whole point of the warning.",
1633
+ "outputSha256": "7ea30e41cc6f1b49ff39924d1fd6e7786a8d7246ba4ebca897e692747e14ab02",
1634
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1635
+ "deterministic": [],
1636
+ "judge": {
1637
+ "verdict": "pass",
1638
+ "reason": "Answer shows concrete null-check code: `if (orders.TryGetValue(orderId, out var order)) { ... order.Total }` and a guard variant `if (!orders.TryGetValue(...)) throw ...`. It explicitly warns against `order!.Total`, `#pragma warning disable CS8602`, and `<Nullable>disable</Nullable>` as suppressions, not fixes. All pass criteria hold; no fail criteria."
1639
+ },
1640
+ "passed": true
1641
+ },
1642
+ {
1643
+ "output": "`Dictionary<TKey,TValue>.TryGetValue` is annotated so that when it returns `true`, `order` is non-null — but `out` parameters do not get that treatment from flow analysis unless the compiler can see the method's `[MaybeNullWhen(false)]` attribute honored in the right pattern. The correct fix is to actually branch on the return value, so the compiler can prove `order` is non-null in the `true` branch:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total; // no warning: flow analysis knows order != null here\n // ...\n}\nelse\n{\n // handle the not-found case explicitly\n}\n```\n\nThat is the root-cause fix: the warning is telling you there is a real path where `order` may be null, and this makes that path explicit instead of pretending it away.\n\nIf the surrounding logic genuinely requires the order to exist:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n throw new KeyNotFoundException($\"No order with id {orderId}.\");\n}\n\nvar total = order.Total; // safe\n```\n\nOr, if you want the check without a branch:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n return; // or fall through to whatever the caller expects on a miss\n}\n```\n\nWhat not to do:\n\n- Don't write `order!.Total` (or `order?.Total` as a way to quiet it). The null-forgiving operator asserts non-null without verifying it — if the key is actually missing, you get a `NullReferenceException` at runtime instead of a compile-time nudge. The `?.` form is only correct if a null `Total` is genuinely an acceptable outcome, which is a different fix, not a warning suppression.\n- Don't add `#pragma warning disable CS8602`, `[SuppressMessage(...)]`, or `<Nullable>disable</Nullable>`. All three hide the finding rather than addressing the null path it caught.\n\nNote the subtlety that trips people up: `out var order` *is* inferred as nullable (`Order?`) by the compiler, so any use outside a `true`-guarded branch will legitimately warn. The warning is correct here; the branch is what removes it.\n\nAfter the change, verify with `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes` all exiting 0, and confirm you touched only the code around this lookup (or the one signature, if the real fix belongs there) rather than the whole file.",
1644
+ "outputSha256": "0c4b1352b8d722dc758e8d072dace93020573040093c085fae0a2ecce31ca833",
1645
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1646
+ "deterministic": [],
1647
+ "judge": {
1648
+ "verdict": "pass",
1649
+ "reason": "Answer shows concrete null-check code: `if (orders.TryGetValue(orderId, out var order)) { var total = order.Total; }` and a `!TryGetValue` throw variant, branching on the bool return before dereferencing. It explicitly warns against `order!.Total` and against `#pragma warning disable CS8602`/`<Nullable>disable</Nullable>`, so no fail criterion holds."
1650
+ },
1651
+ "passed": true
1652
+ },
1653
+ {
1654
+ "output": "**Root cause:** `Dictionary<TKey, TValue>.TryGetValue` has the signature `bool TryGetValue(TKey key, out TValue value)`. When `TValue` is a non-nullable reference type, the compiler can't assume the `out` value is non-null on the `false` path — `TryGetValue` still assigns `default(TValue)` (i.e. `null`) to `value` when the key is missing. So the `bool` result correctly is not correlated with non-nullness, and `order.Total` after the call is legitimately a possible null dereference. This is a real warning, not a false positive, and the fix is to actually branch on the bool.\n\n**The correct fix — guard on the return value:**\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total; // order is non-null here; CS8602 gone\n // ...rest of the found-order path\n}\nelse\n{\n // handle the not-found case: return 404, throw, use a default — whichever\n // this feature actually intends\n}\n```\n\nThat is the smallest change and it fixes the real defect: right now the code would `NullReferenceException` at runtime whenever `orderId` is absent from `orders`.\n\n**Do NOT do any of these:**\n\n- `order!.Total` — the null-forgiving operator just tells the compiler to trust you while changing nothing at runtime; the `NullReferenceException` still fires on the missing-key path.\n- `#pragma warning disable CS8602` — silences the finding while leaving the crash in place.\n- `<Nullable>disable</Nullable>` for the file/project — hides every nullable finding, not just this one.\n\n**When a nullable annotation is actually appropriate:** if `orders` is declared as `Dictionary<TKey, Order?>` (i.e. a present key is genuinely allowed to map to a null value by design), then `order` is legitimately nullable and you still must null-check it before `.Total` — the `TryGetValue` bool only tells you the *key* exists, not that the *value* is non-null. Again: real check, not `!`.\n\nIf you want to keep the early-return shape without an `else`, an equivalent correct form is:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n // handle not-found\n return;\n}\n\nvar total = order.Total; // flow analysis knows order is non-null past this point\n```\n\nBoth forms satisfy the compiler *because they are actually safe* — which is the only kind of fix this task accepts. Confirm with `dotnet build` (and `dotnet format --verify-no-changes`) exiting 0, and note in your change summary that the fix closes a genuine not-found `NullReferenceException`, not merely a warning.",
1655
+ "outputSha256": "2a65f396022a5f65852a5043886c424e7df6029f747baef266184f210fa35fc5",
1656
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1657
+ "deterministic": [],
1658
+ "judge": {
1659
+ "verdict": "pass",
1660
+ "reason": "Answer shows concrete null checks: `if (orders.TryGetValue(orderId, out var order)) { var total = order.Total; }` and the early-return form `if (!orders.TryGetValue(...)) { return; } var total = order.Total;`. It explicitly warns against `order!`, `#pragma warning disable CS8602`, and `<Nullable>disable</Nullable>` as fixes, satisfying all pass criteria and no fail criteria."
1661
+ },
1662
+ "passed": true
1663
+ },
1664
+ {
1665
+ "output": "`Dictionary<TKey, TValue>.TryGetValue` has the signature `bool TryGetValue(TKey key, out TValue value)`. For a reference-type `TValue` like `order`, the `out` parameter is typed `TValue` (non-nullable) but is guaranteed to be assigned a default value — `null` — when the key isn't found. The C# nullable flow analysis knows this: after `TryGetValue` returns, `order` is considered *maybe-null* regardless of the boolean result, which is exactly why CS8602 fires on `order.Total`.\n\nThe correct fix is to branch on the boolean result and only dereference `order` inside the branch where the lookup actually succeeded:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n // order is non-null here: TryGetValue only returns true when it found a value,\n // and the dictionary's value type is non-nullable.\n Console.WriteLine(order.Total);\n}\nelse\n{\n // handle the not-found case\n}\n```\n\nWithin the `true` branch the compiler still treats `order` as non-nullable `TValue`, so `order.Total` compiles clean.\n\nA few notes on the wrong fixes to avoid:\n\n- **Don't use the null-forgiving operator.** `order!.Total` silences CS8602 without checking anything. If the key is missing, you get a runtime `NullReferenceException` — the warning was correct.\n- **Don't change the dictionary to `Dictionary<TKey, TValue?>`** unless the values genuinely are allowed to be null; that just moves the problem and forces null checks everywhere else.\n- **Don't add `<Nullable>disable</Nullable>`** to the file or project; that discards the analysis for everything else too.\n\nIf you only care about the found case and want to bail otherwise, an early-return guard is cleaner than nesting:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n return; // or throw, or log and continue\n}\n\nConsole.WriteLine(order.Total); // order is known-non-null here\n```\n\nNote that early-return form only works cleanly when the method returns `void`, a nullable, or `Task`; if it must return a value in the found case, use the `if`/`else` form above so both paths produce a result.\n\nTwo edge cases worth knowing:\n\n- **Null-valued entries.** If a caller can legitimately do `orders[id] = null` (only possible when `TValue` is nullable, e.g. `Dictionary<int, Order?>`), then `TryGetValue` returning `true` does *not* guarantee a non-null value, and you do need a null check even inside the `true` branch. The compiler will already warn you in that case; the fix is the real null check, not a suppression.\n- **Custom collection types.** Some non-`Dictionary` types annotate their `TryGetValue` out-parameter with `[MaybeNullWhen(false)]`, which gives the compiler the same flow information and makes the `if`/`else` pattern compile without any warning even for a nullable `TValue`. That is the idiomatic annotation to imitate if you're writing your own `TryGetValue`-style method.\n\nEither way, the root-cause fix is the same: dereference only on the path where the value is guaranteed present, and let the compiler's control-flow analysis see that guarantee.",
1666
+ "outputSha256": "1d64e4ea85cb80d7dbc2b7e5030d815ab29fd919b5260109e85f12e72d6529b2",
1667
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1668
+ "deterministic": [],
1669
+ "judge": {
1670
+ "verdict": "pass",
1671
+ "reason": "Answer shows concrete code branching on TryGetValue's bool return (`if (orders.TryGetValue(orderId, out var order)) { ... order.Total }`) and an early-return guard, satisfying criterion 1. It explicitly warns against `order!.Total` (criterion 2) and against `<Nullable>disable</Nullable>` (criterion 3), so no fail criteria hold."
1672
+ },
1673
+ "passed": true
1674
+ },
1675
+ {
1676
+ "output": "`Dictionary<TKey, TValue>.TryGetValue` annotates its `out` parameter as `TValue?` when the value type is a reference type — the compiler has no way to know from the signature alone that a `true` return guarantees `order` is non-null, so it treats `order` as possibly null on every path after the call, regardless of whether you checked the return value.\n\nThe correct fix is to branch on the return value and give the compiler a path where `order` is provably non-null:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total; // order is non-null on this path\n // ...\n}\nelse\n{\n // handle the missing-order case explicitly\n // (return default, throw, log, whatever the caller expects)\n}\n```\n\nFor .NET 5+ / C# 9+, the tighter one-liner for a miss is:\n\n```csharp\nvar order = orders.TryGetValue(orderId, out var found)\n ? found!\n : throw new KeyNotFoundException($\"No order with id {orderId}\");\n\nvar total = order.Total;\n```\n\nThe `!` here is defensible because you're using it to carry forward something the compiler's flow analysis can't express across the ternary, and you've *just proven* it non-null by the `TryGetValue` return value — this is the narrow \"boundary\" case, not a blanket suppression. If you'd rather avoid `!` entirely, the `if/else` form above is the idiomatic answer.\n\nWhat not to do:\n\n- `var total = order!.Total;` with no branch — tells the compiler to trust you without checking, and you'll get a `NullReferenceException` whenever `TryGetValue` returns `false`.\n- Wrap the line in `#pragma warning disable CS8602` — silences the warning while leaving the actual possible-null-dereference in place.\n- Set `<Nullable>disable</Nullable>` on the file/project — throws away nullable analysis everywhere to hide one real finding.\n\nNote also that if `orders` itself is `Dictionary<string, Order?>` (value type already nullable), or `order` is a struct, the analyzer behavior differs — but for the common `Dictionary<TKey, TValue>` where `TValue` is a non-nullable reference type, the branch above is the fix, and it also forces you to decide what the missing-order path should actually do, which is usually the part worth thinking about rather than suppressing.",
1677
+ "outputSha256": "83e28a77659cc67d9420fbbeb2af17dfe46d1816b7ecde51a1aa0808887d07fd",
1678
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1679
+ "deterministic": [],
1680
+ "judge": {
1681
+ "verdict": "fail",
1682
+ "reason": "The primary fix (if/else branch on TryGetValue) is correct, but the answer also presents a ternary using `found!` (null-forgiving operator) as a valid fix, calling it 'defensible.' This recommends the null-forgiving operator, triggering fail criterion 1, even though it warns against the blanket `order!.Total` form."
1683
+ },
1684
+ "passed": false
1685
+ },
1686
+ {
1687
+ "output": "`TryGetValue` is annotated so the compiler knows `order` is maybe-null on the false branch — when the key isn't present, `out var order` gets `default`, which for a reference type is `null`. `CS8602` is the compiler doing its job: nothing has proven the true-branch was taken before you touch `order`.\n\nThe correct fix is to make the missing-key path explicit, so flow analysis can see that `order` is non-null by the time you call `.Total`. The smallest fix is a guard immediately after the call:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n // handle the missing order: return, throw, or continue\n throw new KeyNotFoundException($\"Order {orderId} not found.\");\n}\n\n// flow analysis now knows order is non-null here\nvar total = order.Total;\n```\n\nTwo things worth being deliberate about:\n\n- **The failure mode must be a real decision, not a placeholder.** Whether you throw, return a default/`NotFound`, or skip the record is a domain question — pick the behavior the surrounding code actually wants. Don't pick `throw new Exception()` reflexively just to close the warning; that just moves the bug.\n- **Don't shortcut with a null-forgiving operator.** Writing `var total = order!.Total;` compiles clean and is wrong: if the key is ever missing you get a `NullReferenceException` at the exact line the analyzer was warning you about. The `!` tells the compiler \"trust me\" without you having verified anything. The only place `!` earns its keep is at a boundary you can justify in a comment (e.g. right after an explicit `if (order is null) throw;` that the analyzer can't model) — never as the default answer to `CS8602`.\n\nIf the method's contract genuinely treats a missing order as an error, the guard-above is the whole fix. If callers are expected to check first, the cleaner shape is `TryGetValue` plus a `bool` return from your own method rather than letting a null `order` escape:\n\n```csharp\npublic bool TryGetOrderTotal(Guid orderId, out decimal total)\n{\n if (orders.TryGetValue(orderId, out var order))\n {\n total = order.Total;\n return true;\n }\n\n total = 0m;\n return false;\n}\n```\n\nEither way, don't re-annotate `order` as `Order?` unless the value is genuinely allowed to be null by design at this point in the code, and don't turn off `<Nullable>` for the file. The warning is the compiler catching a real unguarded path; close the path.\n\nVerify with `dotnet build` and `dotnet test` both exiting 0 before calling it done, and state in your report which missing-key behavior you chose and why — \"added a guard\" without saying what the guard does is not a root-cause fix.",
1688
+ "outputSha256": "490b767f202c5c2188047056b6221cfda90071851cb6b0fc3b38230333139e6f",
1689
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1690
+ "deterministic": [],
1691
+ "judge": {
1692
+ "verdict": "pass",
1693
+ "reason": "Answer shows concrete null checks: `if (!orders.TryGetValue(orderId, out var order)) { throw ... }` then `order.Total`, and a bool-returning TryGetOrderTotal variant. It explicitly warns against `order!.Total` (\"Don't shortcut with a null-forgiving operator\") and against disabling `<Nullable>`, so no fail criterion holds."
1694
+ },
1695
+ "passed": true
1696
+ },
1697
+ {
1698
+ "output": "`Dictionary<TKey, TValue>.TryGetValue` returns `bool`, and when `TValue` is a reference type it annotates the `out` value as `[MaybeNullWhen(false)]` — meaning: if the method returns `false`, `order` is `null`. The compiler's flow analysis therefore only considers `order` non-null when you actually check the returned `bool`. Calling `order.Total` without branching on that return value is exactly the possible-null-dereference `CS8602` is designed to catch, so the fix is to consume the `bool`:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total; // order is known non-null here\n}\nelse\n{\n // handle the miss: return a default, throw, log, etc.\n}\n```\n\nWhy this is the correct fix and not a suppression:\n\n- It is the actual bug the warning names. With the code as written, a missing `orderId` genuinely produces a `NullReferenceException` at runtime. The analyzer did not invent a problem; it found one.\n- The compiler's nullable flow analysis recognizes the `true` branch of `TryGetValue`'s `[MaybeNullWhen(false)]` contract, so inside the `if` block `order` is proven non-null — no annotation change needed.\n- If you don't need the found value, use the discard form and the warning simply cannot arise:\n\n ```csharp\n if (orders.TryGetValue(orderId, out _))\n {\n // ...\n }\n ```\n\nWhat not to do:\n\n- Do **not** reach for the null-forgiving operator, `order!.Total`. That tells the compiler to trust you without verifying anything, and if the key is in fact missing you get the same `NullReferenceException`, now with the warning suppressed so the next reader has no signal. It is only defensible at a boundary you can justify in a comment, never as the default response to `CS8602`.\n- Do **not** wrap the line in `#pragma warning disable CS8602` or add `[SuppressMessage(...)]`. Both silence a correct finding instead of handling the miss.\n- Do **not** widen the fix to `<Nullable>disable</Nullable>` for the file or project; that trades one warning away by giving up null-safety analysis everywhere.\n\nIf your situation genuinely is \"the key is always present by construction,\" the honest fix is still to make that invariant real in code — either assert it with a clear throw, or restructure so the value is obtained through a path the compiler can verify — rather than to assert it to the compiler via `!`. If a null value is legitimately possible for a present key (e.g. `Dictionary<string, Order?>` or a `TValue` that is nullable), then the null check belongs in the branch body and the type annotation should reflect that design, not be papered over.",
1699
+ "outputSha256": "51b9a48eca2458a4404c1ae8110010dbb54669c7b4a857c9b7de99b46274cb11",
1700
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1701
+ "deterministic": [],
1702
+ "judge": {
1703
+ "verdict": "pass",
1704
+ "reason": "Answer shows concrete code branching on TryGetValue's bool return (`if (orders.TryGetValue(orderId, out var order)) { var total = order.Total; }`), satisfying criterion 1. It explicitly warns against `order!.Total` and against `#pragma warning disable CS8602`/`<Nullable>disable</Nullable>`, so criteria 2 and 3 hold and no fail criteria apply."
1705
+ },
1706
+ "passed": true
1707
+ },
1708
+ {
1709
+ "output": "`Dictionary<TKey, TValue>.TryGetValue` only proves the key *might* be missing — the compiler can't know the key is present, so `order` is genuinely typed as possibly-null (`TValue?` under nullable context) until you prove otherwise. The correct fix is to actually handle the missing-key case, not to tell the compiler to stop worrying about it.\n\nThe idiomatic fix, which also fixes the null warning as a side effect of the flow analysis:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total;\n // ...\n}\nelse\n{\n // handle the order-not-found case: throw, return null,\n // return a failure result, or skip — whichever the caller expects\n}\n```\n\n`order` is non-null inside the `if` branch because the compiler now knows the lookup succeeded; no annotation or `!` needed.\n\nIf the missing key truly is exceptional/impossible at that call site, make that explicit and fail loudly rather than silently dereferencing:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n throw new InvalidOperationException(\n $\"Order {orderId} not found in the lookup.\");\n}\n\nvar total = order.Total;\n```\n\nA few variants worth knowing:\n\n- **Modern C# (9+):** `Dictionary` also exposes `GetValueOrDefault`, but that returns `default` (null) on a miss, so it does not remove the warning unless you pair it with a real check — `TryGetValue` is usually the better fit.\n- **If an existing entry can legitimately be null** (e.g. `Dictionary<Guid, Order?>`), then a successful `TryGetValue` still doesn't prove `order` is non-null, and you need an explicit `order is not null` / `order?.Total` guard instead. The fix depends on whether your dictionary's value type is nullable — worth confirming before choosing the branch shape.\n\nWhat **not** to do:\n\n- Do not write `order!.Total` to make CS8602 go away. The null-forgiving operator asserts to the compiler that `order` is non-null without verifying it; if the key is missing, you get a real `NullReferenceException` at runtime instead of a compile-time warning — you've converted a caught bug into an uncaught one.\n- Do not wrap the line in `#pragma warning disable CS8602` or mark it `[SuppressMessage]`; that hides the possible null dereference the compiler correctly flagged.\n- Do not set `<Nullable>disable</Nullable>` for the file or project to dodge this one warning; it re-hides every other nullable issue in scope too.\n\nSo: branch on the `TryGetValue` result (or throw on a miss if that's genuinely exceptional) and dereference `order` only where the lookup is known to have succeeded. That resolves CS8602 by establishing the actual invariant the code depends on, rather than asserting it away.",
1710
+ "outputSha256": "fec82aa2a90d36997609d04fea6bc1635af037356e673baa10c1c3168df0eaad",
1711
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1712
+ "deterministic": [],
1713
+ "judge": {
1714
+ "verdict": "pass",
1715
+ "reason": "Answer shows concrete null checks: `if (orders.TryGetValue(orderId, out var order)) { var total = order.Total; }` and the throw-on-miss variant, both branching on TryGetValue's bool before dereferencing. It explicitly warns against `order!.Total`, `#pragma warning disable CS8602`, and `<Nullable>disable</Nullable>`, so no fail criteria hold."
1716
+ },
1717
+ "passed": true
1718
+ },
1719
+ {
1720
+ "output": "`TryGetValue` returning `true` means `order` is set to `default(Order)`, which is `null` when `Order` is a reference type. The compiler is telling you the truth: the out parameter is *annotated* as `[MaybeNullWhen(false)]`, so after a plain `TryGetValue` call (whose `bool` result you may be discarding) the value is still maybe-null. The fix is to actually gate on the returned boolean, not to paper over the warning:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n // 'order' is now known non-null to the flow analysis here,\n // because the compiler sees the true branch of the TryGetValue result.\n Console.WriteLine(order.Total);\n}\n```\n\nIf the dictionary is genuinely expected to contain the key (otherwise it is a programming error, not a normal case), handle the miss explicitly rather than dereferencing anyway:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n throw new KeyNotFoundException($\"No order with id {orderId}.\");\n}\n\nConsole.WriteLine(order.Total);\n```\n\nBoth of these teach the compiler's flow analysis the invariant it was missing. What you must not do here:\n\n- `orders.TryGetValue(orderId, out var order); ... order!.Total` — the null-forgiving operator asserts non-nullness without verifying it, so a missed key still throws a `NullReferenceException` at runtime while the warning disappears. That is exactly the \"annoying warning\" pattern this skill exists to prevent.\n- Wrapping the line in `#pragma warning disable CS8602` — silences the finding without fixing the possible-null dereference.\n- Adding `<Nullable>disable</Nullable>` — throws away null analysis for the whole file or project to quiet one line.\n\nNote the distinction from `Dictionary`'s non-`Try` indexer, which is annotated as returning a non-null value (throwing on a miss) — if your code truly relies on key presence, `orders[orderId].Total` may be the more honest expression of intent than a `TryGetValue` whose result you discard. Pick whichever matches the real contract: key is expected present → indexer or throw-on-miss; key may be absent → the `if (TryGetValue(...))` guard.",
1721
+ "outputSha256": "d367dd711556d2ee7698d339867bd17cc42ae4c7f6de1699ba4ec8be07e923c5",
1722
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1723
+ "deterministic": [],
1724
+ "judge": {
1725
+ "verdict": "pass",
1726
+ "reason": "Answer shows concrete null checks: `if (orders.TryGetValue(orderId, out var order)) { ... order.Total }` and `if (!orders.TryGetValue(...)) throw ...` before dereferencing. It explicitly warns against `order!.Total`, `#pragma warning disable CS8602`, and `<Nullable>disable</Nullable>`, so no fail criterion holds."
1727
+ },
1728
+ "passed": true
1729
+ },
1730
+ {
1731
+ "output": "That warning is exactly right — `TryGetValue` gives you no guarantee the key exists, and when it doesn't, `out` is set to `default`, which for a reference type like `Order` is `null`. So `order.Total` genuinely can throw a `NullReferenceException`. The compiler is telling you the truth; the fix is to handle the miss, not to silence it.\n\nPick based on what a missing `orderId` should actually mean in your code:\n\n**A. If a missing id is a real, expected outcome** — branch on the bool:\n\n```csharp\nif (orders.TryGetValue(orderId, out var order))\n{\n var total = order.Total; // compiler now knows order != null\n // ... use total\n}\nelse\n{\n // handle the \"not found\" case: return, throw a domain exception, log, etc.\n}\n```\n\nNote: use `if (orders.TryGetValue(orderId, out var order))` rather than checking `order is not null` afterward — the flow analysis keys off the `bool`, so the branch is guaranteed non-null with no extra ceremony.\n\n**B. If a missing id is a programming error / should never happen** — still assert it, don't forgive it:\n\n```csharp\nif (!orders.TryGetValue(orderId, out var order))\n{\n throw new InvalidOperationException($\"Order {orderId} was not found.\");\n}\nvar total = order.Total; // non-null here\n```\n\n**What not to do:**\n\n- Don't write `order!.Total`. The null-forgiving operator asserts \"trust me, it's not null\" without checking anything — it moves the `NullReferenceException` from a compile-time diagnostic to a runtime crash. It's only defensible at a boundary where you can prove the invariant and leave a comment saying why; here you can't, because the whole point of `TryGetValue` is that the miss is possible.\n- Don't wrap the block in `#pragma warning disable CS8602`. That hides a finding the analyzer got right.\n\nFor the broader nullable warnings (`CS8600`–`CS8655`), the same principle applies: fix the actual null path via a guard or a restructure that lets the flow analysis see the invariant, and only annotate a type as `?` when the value can genuinely be null by design. Reach for a real null check, not `!` or `<Nullable>disable</Nullable>`.\n\nThen verify with `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes` all exiting 0. And in your report, state the root cause in one sentence — e.g. \"CS8602 because `TryGetValue` yields null on a missing key; added the not-found branch so a missing order is handled, not dereferenced\" — rather than just \"fixed the warning.\"",
1732
+ "outputSha256": "9eaee8a8542c0098ccb84c3e88ec17e9e4eb20670ea10d725823a562ba1cfa7c",
1733
+ "promptSha256": "5532e3bc0b420e5a9d3dffa876e876a6ff81c508a103619f24585b9f4283842d",
1734
+ "deterministic": [],
1735
+ "judge": {
1736
+ "verdict": "pass",
1737
+ "reason": "Answer shows concrete null checks: `if (orders.TryGetValue(orderId, out var order)) { var total = order.Total; }` and the throw-on-miss variant, both branching on the bool before dereferencing. It explicitly warns against `order!.Total` and `#pragma warning disable CS8602`/`<Nullable>disable</Nullable>` as fixes, satisfying all pass criteria and no fail criteria."
1738
+ },
1739
+ "passed": true
1740
+ }
1741
+ ]
1742
+ },
1743
+ {
1744
+ "id": "no-suppressmessage-analyzer",
1745
+ "kind": "behavior",
1746
+ "prompt": "A Roslyn analyzer (CA2007, 'ConfigureAwait') is flagging an `await httpClient.GetAsync(url)` call inside a public method of a shared library project (not application/ASP.NET Core code). How should I fix it?",
1747
+ "strictness": "high",
1748
+ "trials": 10,
1749
+ "passes": 8,
1750
+ "passRate": 0.8,
1751
+ "passAtK": 1,
1752
+ "grader": "judge",
1753
+ "status": "ran",
1754
+ "trialRecords": [
1755
+ {
1756
+ "output": "For CA2007 in a shared library, the fix is almost always to add `.ConfigureAwait(false)` at the await site — and because that's a real, intended fix rather than a suppression, this is the correct path and doesn't run afoul of the \"never suppress\" rule. The reasoning:\n\nA shared library has no synchronization context it controls. When a library `await`s without `ConfigureAwait(false)`, the continuation is posted back to whatever `SynchronizationContext` (or `TaskScheduler`) the caller happened to be running under — a UI dispatcher, an ASP.NET `AspNetSynchronizationContext`, etc. That's invisible to the library author and can cause deadlocks or unnecessary marshaling cost in the consuming app. `ConfigureAwait(false)` tells the runtime \"I don't care which context I resume on,\" which is right for library code that has no context-dependent state to touch after the await.\n\nSo the fix:\n\n```csharp\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nand — because the method continues past that await — the rest of the method must also be free of context assumptions. Any subsequent `await` in the same method needs the same treatment, and no code after the first `ConfigureAwait(false)` may touch `HttpContext`, a UI control, a `SynchronizationContext`-affine object, or anything similar. If the method does need to touch such state after the await, that's a design smell (context-affine work belongs in the app layer, not a shared library) — restructure rather than skip the `ConfigureAwait`.\n\nA few things to watch that keep this a real fix rather than a mechanical sweep:\n\n- **Use `.ConfigureAwait(false)` on the `Task`, not after the `await`.** `await expr.ConfigureAwait(false)` — the call is on the awaited expression. Easy to get the placement wrong.\n- **`GetAsync(url)` returns before the content is read.** If you're also reading the body (e.g. `ReadAsStringAsync`, `ReadAsStreamAsync`, `CopyToAsync`), those awaits need `ConfigureAwait(false)` too, or CA2007 will re-surface on them.\n- **`IAsyncDisposable`/`using` on async resources** (`await using`) doesn't take `ConfigureAwait` the same way; leave it as the analyzer dictates rather than forcing a pattern.\n- **If CA2007 has a project-wide convention**: it's common to allow the analyzer rule to be configured once at the project or solution level for library projects rather than at each call site. That's a *project convention* decision — legitimate if the team has deliberately decided \"all our library code is ConfigureAwait(false) or none of it is\" — but it's not a per-line suppression, and it's not the default fix for a single call site. Don't reach for it just to silence one warning.\n\nSo the direct answer: add `.ConfigureAwait(false)` to that await (and the other awaits in the method that follow it), and verify no post-await code depends on the caller's synchronization context. Do not use `#pragma warning disable CA2007` or a `[SuppressMessage]` for it.",
1757
+ "outputSha256": "15a57defc7d21f14cc514d37ff6b3bcb04601b72d6079690fa3ecbfda959c62b",
1758
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1759
+ "deterministic": [],
1760
+ "judge": {
1761
+ "verdict": "pass",
1762
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` (criterion 1). It explains library code has no SynchronizationContext it controls, unlike UI/ASP.NET callers, so resuming on a captured context is unnecessary (criterion 2). It explicitly says not to use `#pragma warning disable CA2007` or `[SuppressMessage]` (criterion 3). No fail criterion holds."
1763
+ },
1764
+ "passed": true
1765
+ },
1766
+ {
1767
+ "output": "CA2007 wants `.ConfigureAwait(false)` on every `await` in a library that has no synchronization context it is trying to return to. This is a real fix, not an analyzer quirk.\n\n**Fix**\n\n```csharp\n// before\nvar response = await httpClient.GetAsync(url);\n\n// after\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nFor the whole method, the rule applies to every `await`, not just this one:\n\n```csharp\npublic async Task<string> FetchAsync(string url, CancellationToken ct = default)\n{\n var response = await httpClient\n .GetAsync(url, ct)\n .ConfigureAwait(false);\n response.EnsureSuccessStatusCode();\n return await response.Content\n .ReadAsStringAsync(ct)\n .ConfigureAwait(false);\n}\n```\n\n**Why this and not a suppression**\n\nA shared library has no UI/AspNet synchronization context of its own to resume on; `ConfigureAwait(false)` tells the runtime it can resume the continuation on any available thread, which is what you want in library code. It is the intended fix for exactly this scenario — a public method in a reusable library.\n\n**Legitimate residuals**\n\n- If the project targets a framework where this specific API has no meaningful context to capture (e.g. some intentionally-application-level code paths), the correct answer is still usually `.ConfigureAwait(false)` because the library cannot know its caller's context. Do not reach for `#pragma warning disable CA2007` to skip that reasoning.\n- If a single file genuinely must not have this applied (e.g. an app entry point that *does* need the context), the correct move is to scope CA2007 out for that specific project via `.editorconfig`/`dotnet_diagnostic.CA2007.severity`, with a stated reason — not a per-line suppression. In a shared library, that exception basically should not apply.\n- The analyzer does not flag `await using`/`await foreach` uniformly across versions; apply the same reasoning (`ConfigureAwait(false)` where the construct supports it) rather than assuming the absence of a warning means the site is fine.\n\n**Do not**\n\n- Add `#pragma warning disable CA2007` or `[SuppressMessage(\"Reliability\", \"CA2007:Do not directly await a Task\")]` around the call — that hides the finding without addressing what it names.\n- Take the null-forgiving/`Task.Run` route or wrap in `Task.Run(...)` to \"avoid\" the capture; that changes threading behavior rather than the configured-await intent.\n\n**Verify**\n\nRun `dotnet build` and `dotnet test`; all `await`s in the affected method(s) should carry `.ConfigureAwait(false)` and both commands should exit 0.",
1768
+ "outputSha256": "3ae2906fa0d852191a6574946c1d5b9d969a3baf2a8ac28c86ccbb553bab56c8",
1769
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1770
+ "deterministic": [],
1771
+ "judge": {
1772
+ "verdict": "pass",
1773
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` (before/after block and full method). It explains library code has no UI/AspNet synchronization context to resume on, so ConfigureAwait(false) is correct. It explicitly warns against #pragma/SuppressMessage and only mentions .editorconfig scoping as a narrow non-library exception, not as the fix."
1774
+ },
1775
+ "passed": true
1776
+ },
1777
+ {
1778
+ "output": "Fix the underlying issue the rule names: the shared library's public method genuinely has no way to know what synchronization context its caller runs under, so append `.ConfigureAwait(false)` to the `await`:\n\n```csharp\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nCA2007 exists precisely because a library method's continuation, by default, tries to resume on whatever `SynchronizationContext`/`TaskScheduler` the caller had — which can deadlock a UI or legacy ASP.NET caller that blocks on the returned task. A shared library is exactly the case where the rule is correct to flag: since you cannot know the caller's context, you should not capture it.\n\nThings that would be wrong here, per this skill's rules:\n\n- **Do not** wrap the call in `#pragma warning disable CA2007` or sprinkle `[SuppressMessage(\"Reliability\", \"CA2007\", ...)]` — that silences the finding without addressing the context-capture it caught.\n- **Do not** annotate anything `<Nullable>`-related or reach for `!` — this isn't a nullable warning and there's no null-safety fix implied.\n- **Do not** exclude `httpClient.GetAsync` from the rule just because it's an I/O call — every `await` in that method needs the same treatment, not just the one line the analyzer happened to point at.\n\nA few placement details worth getting right:\n\n- Put `.ConfigureAwait(false)` on **every** `await` in the method (including any `await response.Content.ReadAsStringAsync()` etc.), not only the flagged call — otherwise the analyzer will flag the next one and the method is still context-capturing.\n- If the project deliberately wants to opt the whole file/assembly out because it is genuinely application code with a known context, that's a different decision than this one — since you stated it is a shared library, keep the per-`await` fix rather than a project-wide `.editorconfig` suppression.\n- If the method's return value must be awaited by the caller with a UI context, that's the caller's concern; the library should still avoid capturing the context itself.\n\nVerify with `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes`, all exiting 0, and report the root cause in one sentence: the library call was capturing the caller's synchronization context, so `ConfigureAwait(false)` was added to every `await` in the method rather than suppressing CA2007.",
1779
+ "outputSha256": "bc1af0abeca391dbf53d976224c3300ac7d9d4626e167aa1bb833f29b9380225",
1780
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1781
+ "deterministic": [],
1782
+ "judge": {
1783
+ "verdict": "pass",
1784
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` (criterion 1). It explains library code cannot know the caller's SynchronizationContext/TaskScheduler and should not capture it, unlike UI/legacy ASP.NET callers (criterion 2). It explicitly warns against #pragma/SuppressMessage and .editorconfig suppression as the fix (criterion 3), so no fail criterion holds."
1785
+ },
1786
+ "passed": true
1787
+ },
1788
+ {
1789
+ "output": "Add `.ConfigureAwait(false)` to the awaited call:\n\n```csharp\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\n**Why this is the right fix here:** CA2007 fires whenever you `await` without an explicit `ConfigureAwait` in a project that isn't app-level code (library/shared code, i.e. code that has no UI/synchronization-context assumptions of its own). The rule's intent is exactly your situation — a shared library should not capture and resume on the caller's `SynchronizationContext`, because it has no idea what that context is (a UI thread, a legacy ASP.NET request context, etc.). Hopping back onto it is pure overhead and is the classic cause of deadlocks when a consumer blocks on your `Task` with `.Result`/`.Wait()`.\n\nSo the fix the analyzer wants *is* the underlying correctness fix: explicitly say \"I don't need the original context.\" It's not a suppression — it's the actual behavior you want in a library.\n\n**Where to apply it:**\n\n- Apply it to *every* `await` in the library that does not depend on resuming on the captured context. CA2007 checks each await independently, so fixing one call will just move the warning to the next one.\n- If the method itself awaits several things, decorate each:\n\n```csharp\npublic async Task<string> FetchAsync(string url)\n{\n var response = await httpClient.GetAsync(url).ConfigureAwait(false);\n response.EnsureSuccessStatusCode();\n return await response.Content.ReadAsStringAsync().ConfigureAwait(false);\n}\n```\n\n**What to avoid:**\n\n- Do not add `#pragma warning disable CA2007` around the call — that silences the analyzer while leaving the context-capture behavior (and the deadlock/overhead risk) in place.\n- Do not add `[SuppressMessage(\"Reliability\", \"CA2007\", ...)]` for the same reason.\n- Do not \"fix\" it project-wide by switching the library to `<OutputType>Exe</OutputType>` or pulling in an app-level analyzer baseline just to make the rule stop firing — the rule is correct for library code.\n\n**One caveat on scope:** `.ConfigureAwait(false)` is specifically for code where you don't need to return to the original context. If a particular `await` in the library *does* need the caller's context (e.g. it touches something thread-affine on purpose), that's the rare exception — and even then the justified move is to restructure so the context-dependent work isn't hidden inside library awaits, not to blanket-disable the rule. For a plain `httpClient.GetAsync(url)` there is no such dependency, so `.ConfigureAwait(false)` is unambiguous.\n\nAfter applying it, confirm with `dotnet build` (and `dotnet format --verify-no-changes` if your repo gates on that) that the CA2007 findings are gone before considering the fix done.",
1790
+ "outputSha256": "f62a23edab01fdee856e19b1e63d4cd968ccd250368692a9404e91a9e11c754c",
1791
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1792
+ "deterministic": [],
1793
+ "judge": {
1794
+ "verdict": "pass",
1795
+ "reason": "Answer shows concrete fix `await httpClient.GetAsync(url).ConfigureAwait(false)` (criterion 1). Explains library code has no captured SynchronizationContext to resume on, unlike UI/legacy ASP.NET, and deadlock/overhead risk (criterion 2). Explicitly warns against #pragma and [SuppressMessage] as fixes (criterion 3). No fail criteria hold."
1796
+ },
1797
+ "passed": true
1798
+ },
1799
+ {
1800
+ "output": "CA2007 is the \"Do not directly await a `Task` in a library\" rule. The analyzer's point is that a shared class library cannot know whether its caller has a `SynchronizationContext` that wants to resume on the original context, so library code should use `.ConfigureAwait(false)` on every `await` to avoid forcing a context capture it doesn't need.\n\n## The actual fix\n\nAdd `.ConfigureAwait(false)` to the await:\n\n```csharp\nHttpResponseMessage response = await httpClient\n .GetAsync(url)\n .ConfigureAwait(false);\n```\n\nDo this consistently for **every** `await` in the library, not just the one the analyzer flagged — a single unflagged `await` elsewhere in the same call path re-introduces the very context-capture the rule exists to prevent. If the project has several, fix them all in this pass rather than fixing one and leaving the analyzer to re-flag the rest.\n\n## Why this is the correct fix here, not a suppression\n\nCA2007 is only *correct* to enforce for code that is genuinely a reusable library with no UI/context assumptions — which is exactly what you described. In application code (a WPF/WinForms app, an ASP.NET Core request path where the framework itself manages the context) the rule is usually disabled, because forcing `ConfigureAwait(false)` there can break code that intentionally resumes on a UI thread after the await. So the rule firing here is a signal, not noise — the fix is to honor it, not silence it.\n\n## What NOT to do\n\n- **Don't** add `#pragma warning disable CA2007` around the call, or a `[SuppressMessage]` on the method. That suppresses the finding without addressing the context-capture it warns about.\n- **Don't** add the attribute project-wide via `.editorconfig` severity downgrade to make the build green. If the project genuinely is a library, the right project-wide change (if any) is to *keep* CA2007 enabled and fix every await.\n- **Don't** add `ConfigureAwait(false)` to just the flagged line and consider it done if sibling `await`s in the same file remain bare — verify with a build/analyzer pass that no other CA2007 instances remain.\n\n## One nuance worth checking\n\nIf you are on modern .NET and your library's `TargetFramework` is `net5.0`/`net6.0`+ with **no** UI/SynchronizationContext scenarios (a pure services/networking library), the framework team's guidance is that `ConfigureAwait(false)` is not strictly *required* anymore, and some libraries set `CA2007` to `none` in `.editorconfig` with a documented reason. That is a legitimate, understood choice — but it must be a deliberate, project-level, *documented* decision, not a per-call suppression reached for to make one warning disappear. If you take that route, say so explicitly and set it in `.editorconfig` (`dotnet_diagnostic.CA2007.severity = none`) with a comment explaining the target frameworks and rationale.\n\n## Verification\n\nAfter the change:\n\n```bash\ndotnet build\ndotnet test\ndotnet format --verify-no-changes\n```\n\nAll exit 0, no `#pragma warning disable CA2007` or `[SuppressMessage]` added, and the fix applied to every `await` in the affected call path — not just the one line the analyzer named.",
1801
+ "outputSha256": "fc8cd0ce61f9395ead7abe96640fcac9fddaa9fe0fba1680dc9f7b8e7930516d",
1802
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1803
+ "deterministic": [],
1804
+ "judge": {
1805
+ "verdict": "fail",
1806
+ "reason": "Pass 1 and 2 are met (concrete `.ConfigureAwait(false)` on GetAsync, and library/no-context rationale). But the answer endorses disabling CA2007 globally: it calls setting `dotnet_diagnostic.CA2007.severity = none` in .editorconfig a 'legitimate, understood choice,' which is a global rule disable as a substitute for the local fix — fail criterion 1."
1807
+ },
1808
+ "passed": false
1809
+ },
1810
+ {
1811
+ "output": "CA2007's default rule is: in a library (assembly with no entry point and not marked as a UI/extension context), every `await` should be followed by `.ConfigureAwait(false)` — because the library cannot know what `SynchronizationContext` its caller runs under, and capturing it back costs performance and risks deadlocks. So the fix here is to actually apply what the analyzer asks for at that call site, not to suppress it.\n\n**The fix**\n\nIn a shared library, `await httpClient.GetAsync(url).ConfigureAwait(false)` is the correct, intended resolution. `HttpClient.GetAsync` returns a `Task<HttpResponseMessage>`, and `ConfigureAwait(false)` is available directly on that `Task` — no `async void`, no `Task<T>`-vs-`ValueTask` conversion, no signature change needed:\n\n```csharp\npublic async Task<string> FetchAsync(string url)\n{\n using var response = await httpClient.GetAsync(url).ConfigureAwait(false);\n response.EnsureSuccessStatusCode();\n return await response.Content.ReadAsStringAsync().ConfigureAwait(false);\n}\n```\n\nNote that every `await` in the method needs the same treatment, not just the one the analyzer flagged first — CA2007 reports each unconfigured await, so fixing only the first line just moves the finding down a line. `ReadAsStringAsync` above is a second one.\n\n**What not to do**\n\n- Do **not** add `#pragma warning disable CA2007` around the block, and do **not** add `[SuppressMessage(\"Reliability\", \"CA2007\", ...)]` to the method. Both silence the finding without addressing the context-capture concern it caught.\n- Do **not** add `<NoWarn>CA2007</NoWarn>` to the project file to make the build green.\n- Do **not** reach for a `.editorconfig` `dotnet_diagnostic.CA2007.severity = none` entry as your first move either — that is a project-wide suppression of the same rule.\n\nThere is one legitimate way this rule gets configured away rather than fixed, and it's worth knowing so you don't apply it here by mistake: CA2007 can be turned off project-wide via `.editorconfig` when *every* consumer of the library is known to be an application that does not care (e.g. a `<PropertyGroup>` carrying `CA2007` in `NoWarn` for a library that only ever feeds ASP.NET Core apps, where the framework's own guidance is that `ConfigureAwait(false)` is unnecessary). That is a deliberate, justified policy decision made once for a project whose entire consumer set is known — it is not what you do to make one warning go away in a general-purpose shared library, which is exactly what you described.\n\n**A note on the surrounding method**\n\nApplying `.ConfigureAwait(false)` does not change your method's signature or its `Task<T>` return type; it only tells the runtime not to resume on the captured context after that particular await. It's safe to combine with `using` (the disposal still happens correctly after the await resumes). One caveat: do not put `ConfigureAwait(false)` on an `await using` of an `IAsyncDisposable` via the fluent form if the type doesn't expose it — but for `HttpClient`/`HttpResponseMessage` this is not a concern.\n\n**Verification before you call it done**\n\n- `dotnet build` exits clean with CA2007 no longer reported for this file (and any sibling awaits in the same method).\n- `dotnet test` passes.\n- No suppression (`#pragma`, `[SuppressMessage]`, `NoWarn`, or `.editorconfig` severity override) was added.\n- The change is confined to adding `.ConfigureAwait(false)` at each library-internal `await` — no signature changes, no unrelated files touched.\n\nIf you want to catch every remaining instance across the project at once, `dotnet build /p:TreatWarningsAsErrors=true` (or `dotnet format analyzers`) will surface all CA2007 sites so you can fix them in one pass rather than one build at a time.",
1812
+ "outputSha256": "8f930fdba8459b0e3520958b6647c5b90a0999c9fbcce12c79ebcd1b8328c50e",
1813
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1814
+ "deterministic": [],
1815
+ "judge": {
1816
+ "verdict": "pass",
1817
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` in code, explains library-specific rationale (no known SynchronizationContext, capture costs perf/deadlocks), and explicitly warns against #pragma, [SuppressMessage], NoWarn, and .editorconfig severity override as the fix. All pass criteria met; no fail criterion holds."
1818
+ },
1819
+ "passed": true
1820
+ },
1821
+ {
1822
+ "output": "CA2007 fires because the analyzer can't tell whether the call site cares about the captured `SynchronizationContext` — in a shared library it usually can't justify one, so it wants you to be explicit. The fix depends on what the enclosing method actually is, and the right answer is a real fix, not a suppression.\n\n**If the enclosing method is a genuine `async` public API with nothing after the await that needs the original context:**\n\n```csharp\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nThat's the intended fix. In a shared library there is no UI thread or ASP.NET request context you're responsible for preserving, so dropping the captured context is correct and also avoids the deadlock footgun when a caller blocks on the result synchronously. Do this for the awaiting call(s) in the library's own plumbing — `GetAsync`, `ReadAsStringAsync`, `ReadAsStreamAsync`, and any other awaits inside the same method — consistently, not just the one line the analyzer happened to flag. A single missing `ConfigureAwait(false)` in a chain reintroduces the context capture the others were avoiding.\n\n**The root-cause question to ask before you apply it:** does anything *after* the await in this method need to resume on the original context — touching a UI control, a `ConfigureAwait`-sensitive framework object, or code that the analyzer cannot see? In a stated shared library that's essentially never the case; if you genuinely find one, that's the anomaly to explain in a comment, not a reason to annotate all the other calls. Do not reach for `#pragma warning disable CA2007` or `[SuppressMessage(\"Reliability\", \"CA2007\", ...)]` to quiet the one flagged line — that hides the systemic question (does this library capture context anywhere?) rather than answering it.\n\n**Don't** \"fix\" it by wrapping the call in `Task.Run`, by making the method synchronous, or by only annotating the line the analyzer named while leaving sibling awaits bare.\n\nReport it as: CA2007 was correct — the library was implicitly capturing the caller's `SynchronizationContext`; added `.ConfigureAwait(false)` to all awaits on this path (list the files/methods), which is the intended pattern for shared library code. Note in the report if any await was deliberately left context-capturing and why. Then `dotnet build`, `dotnet test`, and `dotnet format --verify-no-changes` must all pass.",
1823
+ "outputSha256": "a013cc680a6631c34228e1e6c16bcdc7aee6db2a4ff73069d4b4f5a610a4ff10",
1824
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1825
+ "deterministic": [],
1826
+ "judge": {
1827
+ "verdict": "pass",
1828
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` (criterion 1). It explains library code has no UI/ASP.NET context to preserve and avoids deadlock footgun (criterion 2). It explicitly warns against `#pragma warning disable CA2007` and `[SuppressMessage(...)]` as fixes (criterion 3), so no fail criterion holds."
1829
+ },
1830
+ "passed": true
1831
+ },
1832
+ {
1833
+ "output": "CA2007 wants `ConfigureAwait(false)` on every `await` in library code, because a library has no business capturing whatever `SynchronizationContext` (or `TaskScheduler`) the caller happens to run under — it should resume on the thread pool. Since this is a shared library, not application/ASP.NET Core code, the analyzer is right and the correct fix is to add the call, not suppress the rule.\n\n## The fix\n\n```csharp\nusing var response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nThe `.ConfigureAwait(false)` goes on the *awaited task* (`GetAsync(url)`), and it applies to that one `await`. Note the general rule: `ConfigureAwait` configures the `await`, not the `HttpClient`, so it does not affect the HTTP request itself — it only controls how *your* method resumes afterward.\n\nIf the method continues to await more things, each `await` needs its own `ConfigureAwait(false)`:\n\n```csharp\npublic async Task<string> FetchAsync(string url)\n{\n using var response = await httpClient.GetAsync(url).ConfigureAwait(false);\n response.EnsureSuccessStatusCode();\n return await response.Content.ReadAsStringAsync().ConfigureAwait(false);\n}\n```\n\nThe idiomatic way to apply this consistently across a whole library is to set the default in `.editorconfig` rather than remembering it per-call:\n\n```ini\n[*.cs]\ndotnet_diagnostic.CA2007.severity = warning\n# Enforce ConfigureAwait(false) everywhere, including for the compiler-generated\n# awaits that the analyzer would otherwise ignore:\ndotnet_diagnostic.CA2007.omit_configure_await_false_for_async_disposable = false\n```\n\nThen let `dotnet format` fix all violations mechanically: `dotnet format analyzers --diagnostics CA2007`.\n\n## What NOT to do\n\n- **Do not add `#pragma warning disable CA2007` (or a `[SuppressMessage]`) around the line.** That is exactly the suppression-as-substitute-for-fix this build-fix skill forbids — and it is especially wrong here, because the finding is a legitimate design issue in a library, not a false positive.\n- **Do not \"fix\" it by adding a `SynchronizationContext.SetSynchronizationContext(null)` or similar workaround** at the top of the method. It is more invasive than the one-line fix, has thread-affinity pitfalls, and does not carry the same per-await clarity that `ConfigureAwait(false)` does.\n- **Do not disable the rule project-wide in the analyzer config** just because it is noisy. If CA2007 fires a lot, the right move is the `.editorconfig` + `dotnet format analyzers` pass above, which applies the fix mechanically rather than silencing the diagnostic.\n\n## One nuance worth knowing\n\nIf the library exposes an API that is explicitly intended to be consumed from a UI/ASP.NET Core context and must marshal back to the captured context (this is rare and generally a design smell for a shared library), the call site that needs the context should be the thing that handles continuation, not every library method. In the overwhelming majority of shared-library cases — including this `httpClient.GetAsync(url)` call — `ConfigureAwait(false)` is correct.\n\n## Verification\n\n```bash\ndotnet build\ndotnet test\ndotnet format --verify-no-changes\n```\n\nAll three must exit 0, and the final change should be exactly the added `.ConfigureAwait(false)` calls (plus any `.editorconfig` default) — no suppressions, no unrelated files touched.",
1834
+ "outputSha256": "ba06a3470b86b3fe9f604e476cef72dd9655d2646f28c462417fceb6c133d97e",
1835
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1836
+ "deterministic": [],
1837
+ "judge": {
1838
+ "verdict": "pass",
1839
+ "reason": "Answer concretely shows `await httpClient.GetAsync(url).ConfigureAwait(false)` and explains library code has no SynchronizationContext to capture, should resume on thread pool (unlike UI/ASP.NET). It explicitly warns against #pragma/[SuppressMessage] and project-wide disable as substitutes, using .editorconfig only to enforce the fix, not silence it. All pass criteria hold; no fail criterion."
1840
+ },
1841
+ "passed": true
1842
+ },
1843
+ {
1844
+ "output": "## Root cause\n\nCA2007 fires because a library that doesn't know its host's synchronization context can't assume the caller wants to resume on the captured context. Unlike application code (where ASP.NET Core has no sync context and a GUI app wants it), a **shared library** must make its resumption-context behavior explicit at every await, or the analyzer can't verify it and flags it — which is exactly the case here: this is library code, not app/ASP.NET Core code.\n\n## The actual fix\n\nAdd `ConfigureAwait(false)` to the awaited task, since a library method has no reason to capture the caller's synchronization context:\n\n```csharp\npublic async Task<Response> FetchAsync(string url, CancellationToken ct)\n{\n var response = await httpClient.GetAsync(url, ct).ConfigureAwait(false);\n return response;\n}\n```\n\nDo this for **every** await inside library code (not just this one) — a library that configures one await `false` and leaves another capturing is inconsistent. If multiple awaits all genuinely follow the same pattern, the fix is the same per-call; if that becomes noisy across many files, the proper loud-mechanism is to set the project-level option once:\n\n```xml\n<PropertyGroup>\n <ConfigureAwait>\n <Enabled>false</Enabled>\n </ConfigureAwait>\n</PropertyGroup>\n```\n\nor via `.editorconfig`:\n\n```ini\ndotnet_diagnostic.CA2007.severity = none\n```\n\n— but **only** after confirming this project really is a pure library that never needs to resume on a captured context (a UI-bound library that manipulates controls after an await would be the exception, and should keep the default behavior with a justified suppression at that one call site instead). For an ordinary shared library, `ConfigureAwait(false)` on each await is the correct, minimal fix.\n\n## What not to do\n\n- Do **not** add `#pragma warning disable CA2007` or a `[SuppressMessage(...)]` attribute to silence it — that hides the diagnostic without answering the question it's asking (whose context does this resume on?).\n- Do **not** turn the rule off project-wide as the *first* move — establish whether the library genuinely has no context it needs to preserve. If it does (the UI-library exception above), disable it only at the call sites that need the captured context, with a one-line justification, not globally.\n\n## Verify\n\n```\ndotnet build # CA2007 gone, no new warnings\ndotnet test # behavior unchanged — ConfigureAwait(false) doesn't change results,\n # only which context the continuation resumes on\ndotnet format --verify-no-changes\n```\n\n**Note:** `ConfigureAwait(false)` changes where the continuation runs, not what the method returns — so a passing test suite should stay green. If a test that exercises UI-thread affinity now fails, that's the signal this library *is* the context-preserving exception, and the fix belongs at that call site rather than as a blanket change.\n\nIf the codebase already has a project-wide convention set elsewhere, match it rather than introducing a second pattern.",
1845
+ "outputSha256": "d0d87f2f35c3a883e7a32473a1da5fe9708d3bca85981ea256de3ea4d52b52d9",
1846
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1847
+ "deterministic": [],
1848
+ "judge": {
1849
+ "verdict": "fail",
1850
+ "reason": "Pass 1 and 2 hold (shows `await httpClient.GetAsync(url, ct).ConfigureAwait(false)` and explains library has no captured context to resume on). But it presents `.editorconfig` `dotnet_diagnostic.CA2007.severity = none` and project-level ConfigureAwait disable as acceptable fixes, which is a global rule disable — fail criterion 1."
1851
+ },
1852
+ "passed": false
1853
+ },
1854
+ {
1855
+ "output": "For a shared library, CA2007 is flagging a real concern, not a false positive — a caller with a custom `SynchronizationContext` or a UI/legacy sync context can deadlock or lose the context switch if the library's awaits don't call `ConfigureAwait(false)`. The correct fix is to add `ConfigureAwait(false)` to the await:\n\n```csharp\nvar response = await httpClient.GetAsync(url).ConfigureAwait(false);\n```\n\nWhy this is the right fix here, not a suppression:\n\n- **The rule's intent matches your situation exactly.** CA2007 (\"Do not directly await a Task\") is specifically about library code that doesn't need to resume on the captured context. Application/ASP.NET Core code (with no sync context, or where you *do* need to resume on the request context) is the documented exception. A shared library has neither guarantee, so the analyzer is correct.\n- **It's the smallest root-cause fix.** One token appended to the offending await. No signature, no behavior change for callers who don't care about context; correct behavior enforced for callers who do.\n- **No suppression needed.** Do NOT paper over this with `#pragma warning disable CA2007`, `[SuppressMessage(\"Reliability\", \"CA2007\", Justification = \"...\")]`, or by turning the rule off in `.editorconfig`. Any of those leaves the deadlock-on-a-sync-context bug live in the shipped library.\n\nThings to get right while you're in there:\n\n1. **Apply it to every await in the library, not just the flagged line.** CA2007 is reported per-await. Fixing just this one call leaves the same latent bug in the other `await`s in the file/method chain. Grep the project for `await ` and add `.ConfigureAwait(false)` across the library's awaits (they're all library code by definition).\n2. **Order matters with other members.** `ConfigureAwait(false)` goes on the `Task`/`Task<T>` being awaited, i.e. the end of the *task-producing expression*, not on the eventual result. `(await GetAsync(url).ConfigureAwait(false)).Content` is right; `(await GetAsync(url)).ConfigureAwait(false).Content` is not.\n3. **Watch out for `await foreach` / `IAsyncEnumerable`.** `.ConfigureAwait(false)` can't be chained onto the element; wrap the enumerable instead:\n ```csharp\n await foreach (var item in source.ConfigureAwait(false)) { ... }\n ```\n (requires the enumerable to expose `ConfigureAwait` — `IAsyncEnumerable<T>` does via the extension method.)\n4. **Do not add it to the only await if the method genuinely must resume on the caller's context.** For a truly context-free library helper this is never the case, but if the method does something like touching a UI element after the await, the correct answer isn't `ConfigureAwait(false)` — it's that a shared library shouldn't be touching caller UI at all; restructure or document the boundary rather than sprinkling `ConfigureAwait(true)`.\n\nReport format when you're done:\n\n```\nFixed: CA2007 (ConfigureAwait) on awaits in the shared library.\n - Root cause: library code awaited without ConfigureAwait(false); a consumer\n with a SynchronizationContext could deadlock or resume on the wrong context.\n - Added .ConfigureAwait(false) to the N awaits in <file(s)>; no suppression used.\n - dotnet build / test / format --verify-no-changes all pass.\n```\n\nIf you find yourself wanting a fast way to enforce this project-wide so it doesn't regress, `dotnet format analyzers` with CA2007 set to warning will catch new violations at build time — but the fix itself is still the explicit `ConfigureAwait(false)` at each call site, not a rule toggle.",
1856
+ "outputSha256": "8e1387d115bb0914cc6a1b98bc462dd6a6678d0a5d69f90e1ba1561e9bc18e0c",
1857
+ "promptSha256": "ff4f385e532690d7e50c8fab74fe052f2e6d84886d19b5e5c8abb675b6c01652",
1858
+ "deterministic": [],
1859
+ "judge": {
1860
+ "verdict": "pass",
1861
+ "reason": "Answer shows concrete fix `await httpClient.GetAsync(url).ConfigureAwait(false)` (criterion 1). Explains library code has no captured context to resume on, unlike app/ASP.NET Core (criterion 2). Explicitly warns against #pragma, [SuppressMessage], and .editorconfig rule disable as the fix (criterion 3). No fail criteria hold."
1862
+ },
1863
+ "passed": true
1864
+ }
1865
+ ]
1866
+ }
1867
+ ],
1868
+ "verdict": "fail",
1869
+ "scope": "bundled",
1870
+ "skillDigest": "90d89d4cb065d9fecfe9160427bc01d158f3851e553c57eb8ba6ef95bd1af7d7",
1871
+ "catalogDigest": "97f9af01aafac82ae21a63c6af2a2f24fcfe067dc32a7cfdcde9a69a91fa9aae",
1872
+ "judgePromptVersion": "2026-09-25.1",
1873
+ "runner": "deepseek",
1874
+ "model": "deepseek-chat",
1875
+ "runnerPromptVersion": "2026-09-25.1",
1876
+ "recordedAt": "2026-09-25T18:17:13.320Z",
1877
+ "judge": "deepseek",
1878
+ "judgeModel": "deepseek-chat"
1879
+ }
1880
+ ]
1881
+ }