Skip to content

Reinstate the xUnit1051 analyzer (GH-3702) - #3720

Merged
jeremydmiller merged 3 commits into
mainfrom
fix/3702-reinstate-xunit1051
Jul 29, 2026
Merged

Reinstate the xUnit1051 analyzer (GH-3702)#3720
jeremydmiller merged 3 commits into
mainfrom
fix/3702-reinstate-xunit1051

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3702 for 71 of the 73 xUnit projects.

xUnit1051 ("use TestContext.Current.CancellationToken") was suppressed repo-wide in Directory.Build.props during the v3 migration, because TreatWarningsAsErrors turned it into a hard build break across 2,264 call sites (8,140 raw warnings once multi-targeting is counted).

The fix comes from xUnit's own code fix

Not by hand, not by regex. dotnet format analyzers cannot drive this one: Xunit.Analyzers.Fixes.UseCancellationTokenFixer returns null from GetFixAllProvider(), and dotnet format only applies fixers that support FixAll — it reports "Format complete" and changes nothing. So a small Roslyn driver loads the analyzer and invokes the CodeFixProvider directly, one compilation per pass, merging the disjoint TextChanges per file.

Three things that driver had to account for, each of which a blind sweep gets wrong:

The tool's own diagnostic count cannot be trusted. An MSBuildWorkspace load on a cold project can come back with an incomplete compilation and report zero diagnostics. CoreTests reported 0, then 472 on retry. So the build — with xUnit1051 back at error severity — is the gate, and the driver re-runs the fixer while anything is still reported. Anything that trusted the tool alone would have silently under-fixed.

The fixer sometimes binds the wrong named parameter. On InvokeAsync<T>(object, CancellationToken, TimeSpan?) it emitted timeout: TestContext.Current.CancellationToken. CS1503 caught both occurrences.

It declines some shapes outrightTask.Run(() => …) and Task.WhenAny(tcs.Task, Task.Delay(…)). Twelve sites threaded by hand.

Where the analyzer's advice is wrong

It fires inside NSubstitute verifications, and taking it there is actively harmful:

await channel.Received().QueueDeclareAsync(..., cancellationToken: TestContext.Current.CancellationToken);

That narrows the verification to that exact token, which the production call does not pass — a guaranteed runtime failure that compiles fine. Those 25 sites use Arg.Any<CancellationToken>() instead, which the analyzer accepts.

Per-project opt-in, not a flag flip

Directory.Build.targets carries a conditional NoWarn, so a project opts in with one line:

<XUnitCancellationTokenEnforced>true</XUnitCancellationTokenEnforced>

It has to live in .targets rather than .props because the property is set by the project file, which is evaluated after Directory.Build.props. The block gets deleted once the last two projects are in.

What is and isn't verified

The two projects left out are SampleTests and TracingTests, which do not compile at all on main — that is #3704.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JMNKwGVHnyaBjiheC5k8KX

jeremydmiller and others added 3 commits July 29, 2026 14:17
…GH-3702)

xUnit1051 ("use TestContext.Current.CancellationToken") was suppressed repo-wide in
Directory.Build.props during the v3 migration, because TreatWarningsAsErrors turned it
into a hard build break across 2,264 call sites (8,140 raw warnings once multi-targeting
is counted).

The fix is applied by xUnit's OWN code fix, not by hand and not by a regex. `dotnet format
analyzers` cannot drive it -- Xunit.Analyzers.Fixes.UseCancellationTokenFixer returns null
from GetFixAllProvider(), and dotnet format only applies fixers that support FixAll -- so a
small Roslyn driver loads the analyzer and invokes the CodeFixProvider directly, one
compilation per pass, merging the disjoint TextChanges per file.

Three things that driver had to account for, each of which a blind sweep gets wrong:

- The tool's own diagnostic count cannot be trusted. An MSBuildWorkspace load on a cold
  project can return an incomplete compilation and report ZERO diagnostics -- CoreTests
  first reported 0, then 472 on retry. The build, with xUnit1051 back at error severity,
  is the gate; the driver re-runs the fixer while anything is still reported.

- The fixer sometimes binds to the wrong named parameter. On
  `InvokeAsync<T>(object, CancellationToken, TimeSpan?)` it emitted
  `timeout: TestContext.Current.CancellationToken`. CS1503 caught both occurrences.

- It declines some shapes outright: `Task.Run(() => ...)` and
  `Task.WhenAny(tcs.Task, Task.Delay(...))`. Twelve sites threaded by hand.

Separately, the analyzer fires inside NSubstitute verifications, where taking its advice is
actively wrong: `channel.Received().QueueDeclareAsync(..., cancellationToken: TestContext
.Current.CancellationToken)` narrows the verification to that exact token, which production
code does not pass. Those 25 sites use `Arg.Any<CancellationToken>()` instead, which the
analyzer accepts.

Rather than a global flag flip, Directory.Build.targets carries a conditional NoWarn so each
project opts in with

    <XUnitCancellationTokenEnforced>true</XUnitCancellationTokenEnforced>

It has to live in .targets because the property is set by the project file, which is
evaluated after Directory.Build.props. Delete the block once the last two projects are in.

71 of 73 xUnit projects are converted. The two left out are SampleTests and TracingTests,
which do not compile at all on main -- that is GH-3704.

Verified: `dotnet build wolverine.slnx -c Release` succeeds. CoreTests runs 2104 total /
2101 passed / 1 failed, the same single pre-existing failure main has (GH-3703, fixed
separately in #3717) -- i.e. no movement from this change. Remaining suites are
compile-verified only and rely on CI; anything that misbehaves will do so at runtime, not
at build time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JMNKwGVHnyaBjiheC5k8KX
…the test's (GH-3702)

The xUnit1051 sweep threaded `TestContext.Current.CancellationToken` into NSubstitute
mock interactions. On a verification that is merely narrowing, and on an arrange it is
worse than narrowing: it makes the stub match only that exact token. Production code
passes its own token -- `default` in every one of these paths -- so the arranged call
never matches, NSubstitute hands back `null`, and the test dies dereferencing it.

That is the 11 consistently-failing tests on this PR's last CI run, all of them
`NullReferenceException` under a transport's endpoint-initialization unit tests:

  Wolverine.AmazonSns.Tests      when_initializing_the_endpoint            (1)
  Wolverine.AmazonSqs.Tests      when_initializing_the_endpoint            (4)
  Wolverine.AzureServiceBus.Tests AzureServiceSubscriptionTests            (1)
  Wolverine.RabbitMQ.Tests       Internals.RabbitMqQueueTests              (5)

The original commit already knew verifications had to use `Arg.Any<CancellationToken>()`
and converted 25 of them; it just did not carry the same rule to the arrange side. The
rule is simply: NSubstitute interactions match on arguments, so a mock call -- arrange
or assert -- takes `Arg.Any<CancellationToken>()`. Never the ambient test token.

13 sites across 5 files. Found by tokenizing every file into statements and flagging any
`TestContext.Current.CancellationToken` inside a statement carrying an NSubstitute marker
(`.Returns`/`.Received`/`Arg.*`/...). A second, independent pass -- flag the token whenever
it is an argument to a call on a variable the file builds with `Substitute.For<>` -- now
reports zero, so this clears the class and not just the failures CI happened to surface.

Verified: red baseline reproduced locally before the fix (Wolverine.AmazonSqs.Tests
`when_initializing_the_endpoint` = 4 failed / 4 passed, same 4 as CI), then 0 failed /
8 passed after. All four projects build with xUnit1051 at error severity, 0 warnings.
SNS 2/2, SQS 8/8, ASB 5/5, RabbitMQ 25/25 (includes native_dead_letter_queue_mechanics
against a live broker, whose two sites were latent -- they had not yet failed on CI).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 0a92b4c into main Jul 29, 2026
31 of 32 checks passed
jeremydmiller added a commit that referenced this pull request Jul 29, 2026
… tests

GH-3702 reinstated the xUnit1051 analyzer on main after this branch was cut, and CI
builds the merge commit, so these six WaitAsync calls and one Task.Delay became hard
build errors the moment #3720 landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ojaHNzLcE8m5krwfTNPxw
jeremydmiller added a commit that referenced this pull request Jul 30, 2026
… on every node (GH-3698) (#3723)

* fix(agents): drain agent commands per destination instead of one at a time (GH-3698)

The leader's agent command drain executed one command at a time across the
whole cluster. Combined with NodeAgentController.batchCommands, which groups a
wave's AssignAgent commands by destination and appends each destination's
chunks contiguously, that made the drain destination-at-a-time: every chunk for
node A was awaited to completion before node B was sent its first.

Cluster-wide agent start concurrency was therefore capped at a single node's
MaxAgentStartParallelism no matter how many nodes were in the cluster, so
adding nodes did nothing for convergence time. On a cluster of ~5,240
slow-starting Marten subscription agents that is hours of one node working
while its peers sit idle. And because executeHealthChecks awaits the drain
before the next EvaluateAssignmentsAsync, the leader also stopped writing
AssignmentChanged records for the whole duration -- which is why the reported
cluster looked stalled rather than merely slow, with no errors and every node
healthy.

IAgentCommand gains a `Guid? DestinationNodeId` default interface member, so
existing and third-party commands stay source-compatible. The new
AgentCommandDrain partitions each round into one lane per destination: serial
within a lane (one chunk in flight per node, which is what the WO-5
pending-assignment ledger assumes), concurrent across lanes. Commands naming no
single destination share one strictly-serial lane, exactly as before. Cascades
are deferred to the next round rather than appended to the producing lane, so
"a cascade runs after its producer" still holds while being re-keyed onto the
right lane.

A failing command no longer discards the rest of the wave -- previously any
exception, most realistically a TimeoutException from an AssignAgents chunk
that outran its reply window, unwound out of the drain and dropped every
command still queued for every other node. Failures are returned rather than
thrown so ApplyRestrictionsAsync, which answers to an operator, still surfaces
them as an AggregateException once the whole wave has run instead of silently
reporting success.

Measured on the new SlowTests repro (900 agents, 3 nodes, 1s per agent start,
shipped defaults): peak cluster-wide concurrent starts 10/30 -> 30/30, time to
converge 125.5s -> 57.2s, and all three nodes now gain agents in lockstep
instead of one at a time.

Still open, tracked separately: the drain continues to block the assignment
evaluation itself, so the leader's AssignmentChanged gap shrinks from 103s to
35s but does not go away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(agents): pass the test cancellation token in the new agent drain tests

GH-3702 reinstated the xUnit1051 analyzer on main after this branch was cut, and CI
builds the merge commit, so these six WaitAsync calls and one Task.Delay became hard
build errors the moment #3720 landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ojaHNzLcE8m5krwfTNPxw

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 30, 2026
…ng assignment as grid state (GH-3698) (#3719)

* fix(agents): drain agent commands per destination instead of one at a time (GH-3698)

The leader's agent command drain executed one command at a time across the
whole cluster. Combined with NodeAgentController.batchCommands, which groups a
wave's AssignAgent commands by destination and appends each destination's
chunks contiguously, that made the drain destination-at-a-time: every chunk for
node A was awaited to completion before node B was sent its first.

Cluster-wide agent start concurrency was therefore capped at a single node's
MaxAgentStartParallelism no matter how many nodes were in the cluster, so
adding nodes did nothing for convergence time. On a cluster of ~5,240
slow-starting Marten subscription agents that is hours of one node working
while its peers sit idle. And because executeHealthChecks awaits the drain
before the next EvaluateAssignmentsAsync, the leader also stopped writing
AssignmentChanged records for the whole duration -- which is why the reported
cluster looked stalled rather than merely slow, with no errors and every node
healthy.

IAgentCommand gains a `Guid? DestinationNodeId` default interface member, so
existing and third-party commands stay source-compatible. The new
AgentCommandDrain partitions each round into one lane per destination: serial
within a lane (one chunk in flight per node, which is what the WO-5
pending-assignment ledger assumes), concurrent across lanes. Commands naming no
single destination share one strictly-serial lane, exactly as before. Cascades
are deferred to the next round rather than appended to the producing lane, so
"a cascade runs after its producer" still holds while being re-keyed onto the
right lane.

A failing command no longer discards the rest of the wave -- previously any
exception, most realistically a TimeoutException from an AssignAgents chunk
that outran its reply window, unwound out of the drain and dropped every
command still queued for every other node. Failures are returned rather than
thrown so ApplyRestrictionsAsync, which answers to an operator, still surfaces
them as an AggregateException once the whole wave has run instead of silently
reporting success.

Measured on the new SlowTests repro (900 agents, 3 nodes, 1s per agent start,
shipped defaults): peak cluster-wide concurrent starts 10/30 -> 30/30, time to
converge 125.5s -> 57.2s, and all three nodes now gain agents in lockstep
instead of one at a time.

Still open, tracked separately: the drain continues to block the assignment
evaluation itself, so the leader's AssignmentChanged gap shrinks from 103s to
35s but does not go away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(agents): compare batched agent commands by their agent URIs (GH-3698)

Every batched agent command carries a Uri[], and the record default compares
those arrays by reference -- so two commands naming exactly the same agents
were never equal. StartAgents and StopAgents had hand-written SequenceEqual
overrides for that reason, but both left GetHashCode returning the array's
reference hash: a hash that disagrees with equality means the two values never
land in the same bucket and are never compared at all, so those overrides did
nothing in any hash-based lookup. AssignAgents, StopRemoteAgents, AgentsStarted
and AgentsStopped had no override at all.

All six now share AgentUriSet, which compares by the actual Uri values and is
deliberately order-independent: nothing about a batch depends on the order its
URIs happen to be in, and two assignment waves can chunk the same set of agents
in a different order. It is a multiset comparison, so a repeated URI still has
to be matched by the same number of repeats rather than collapsing away, and
the hash is summed rather than order-sensitively combined so any ordering of
the same agents hashes alike.

This matters for anything that uses equality to recognise the same work twice --
notably the pending-command suppression in the agent command pump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* wip(agents): decouple the assignment evaluation from the command drain (GH-3698)

NOT FINISHED -- SlowTests/Agents/agent_assignment_at_scale.cs currently FAILS on
this commit. Pushed as part of a draft PR for review, see below.

executeHealthChecks used to await DoHealthChecksAsync() and then the whole agent
command drain in the same loop body, so for as long as a wave of agent starts
was in flight, EvaluateAssignmentsAsync did not run at all -- and since the
observer early-returns on an empty command list, the leader wrote ZERO
AssignmentChanged records the entire time. That is why the reported cluster
looked stalled and idle rather than merely slow. Same defect as GH-3604/D1,
which decoupled the heartbeat; this decouples the other half.

The health-check loop now writes commands to a channel and ticks on; a
drainAgentCommands pump executes them. A _queuedCommands set suppresses
enqueuing a command equal to one already queued or in flight, so a wave
re-emitted after the pending-assignment ledger's TTL is not issued against
itself.

Measured on the repro: longest leader evaluation gap 103s -> 1.1s, and the
leader keeps writing AssignmentChanged throughout a wave. Peak cluster-wide
start concurrency still 30/30.

WHY IT IS NOT DONE: convergence regressed 57.2s -> 83.6s. Once evaluation runs
during a wave, the distribution pass re-decides placement for every agent that
has been dispatched but has not started yet -- Agent.OriginalNode comes from
persisted ActiveAgents, so an in-flight agent looks entirely unassigned.
pinPendingAssignments holds such an agent on the node it was already dispatched
to, which stopped the re-emission flood, but it runs AFTER the families
distribute: the distribution balances all three nodes evenly and the pin then
moves agents back, leaving one node over its share. The observed tail is one
node finishing ~300 agents alone while the other two sit idle.

Next step is to pin BEFORE the family distribution, which needs care in
DistributeEvenly / DistributeEvenlyWithBlueGreenSemantics -- both open by
detaching everything a node holds beyond `maximum`, which would undo a pin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(agents): hold a dispatched-but-unstarted agent on its node while a wave drains (GH-3698)

Completes the previous commit, which is now green: agent_assignment_at_scale
converges in 69.4s with a longest leader evaluation gap of 1.0s.

Once the assignment evaluation no longer waits for the command drain, an
evaluation lands every HealthCheckPollingTime while a wave of slow agent starts
is still in flight -- and the distribution pass re-decides placement for every
agent that has been dispatched but has not started yet, because
Agent.OriginalNode comes from each node's PERSISTED ActiveAgents and the
assignment row only appears once the agent is actually running. A re-targeted
agent defeats the pending-assignment ledger, which is keyed on (agent -> node):
the suppression stops working, every pass re-emits the whole agent universe, and
the AssignmentChanged flood of GH-3604/D6 comes straight back. Worse, the agent
ends up dispatched to two nodes at once.

pinPendingAssignments holds such an agent on the node it was already dispatched
to. It runs AFTER restrictions -- an operator's pause or explicit pin outranks a
dispatch we have not managed to complete, and re-placing a paused agent here
would resurrect the GH-3663 class of bug -- and BEFORE the family distributes,
which is the part that matters: the distribution then sees those agents as
already placed, counts them toward their node's share, and balances the
genuinely unassigned remainder around them. Pinning after the fact instead
leaves the distribution's own even split intact and moves agents on top of it,
which overloads whichever node the pins land on (that cost 83.6s vs 69.4s).

Deliberately does not touch Agent.OriginalNode: the ledger confirms entries by
matching OriginalNode, so fabricating one here would make every pending entry
instantly self-confirming.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(agents): suppress queued agent starts by agent URI, not just by command (GH-3698)

Command-level suppression in the agent command pump was not enough. The
pending-assignment ledger releases an agent after its TTL, the next evaluation
re-emits the still-unstarted remainder, and batchCommands re-chunks it -- so the
new chunks carry DIFFERENT membership than the ones the pump is still working
through (900 agents chunk into 18 batches of 50; the 650 not yet started chunk
into 13). No command-level comparison can recognise those as the same work, so
agents were started a second time while their first start was still in flight.

Suppress at the agent level instead: an incoming batch is narrowed to the agents
nothing already has queued or in flight, and dropped outright if that leaves
nothing. Deliberately only for first-time placements -- AssignAgent and
AssignAgents. A ReassignAgent also stops the agent on its previous node, so
suppressing one because a start is pending would drop that stop, and the stop
commands are not starts at all.

Measured on the repro, all four runs on the same freshly created Postgres
container:

  before: peak cluster-wide concurrency 20 of 30, converged 89.3s / 90.4s
  after:  peak cluster-wide concurrency 30 of 30, converged 71.3s / 75.2s / 76.3s

The concurrency figure is the interesting one: redundant re-chunked work was
crowding a destination out of the pump's batch, so a batch often covered only
two of the three nodes.

Also recalibrates the convergence budget in agent_assignment_at_scale from 75s to
100s. The old value was derived from start work alone (~52s) and ignored that one
chunk is in flight per destination at a time, so each lane additionally pays a
control-queue request/reply round-trip per chunk -- six per node here, over the
POLLED dbcontrol transport. That is ~20s of latency rather than wasted work, and
it shrinks with a larger AgentStartBatchSize, which is what the reporter observed
helping. The pre-fix behaviour was 125s so the assertion still catches the
defect, and the peak-concurrency and evaluation-gap assertions are the precise
ones.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* wip(agents): persistent per-destination lanes for the agent command dispatcher (GH-3698)

NOT SUFFICIENT ON ITS OWN. The RavenDb leadership-election suite is still red on
this branch; see below. Kept because the work and its tests are correct as far as
they go, and the eventual fix builds on them.

Replaces the one-wave-at-a-time pump with AgentCommandDispatcher: a long-lived
lane (channel + worker) per destination node, serial within a lane, concurrent
across lanes, cascades routed back through the dispatcher so a ReassignAgent's
cascaded AssignAgent lands in the lane of the node it targets. This removes the
head-of-line blocking the pump had -- a wave holding an AssignAgents aimed at a
just-removed node blocked on its 30s+chunkSize reply window while the re-targets
to surviving nodes queued behind it and never ran.

Also: in-flight suppression is keyed on (agent uri -> destination) rather than the
agent alone. A re-target is a different instruction, not a duplicate of the copy
in flight, and keying on the agent alone stranded it for as long as the doomed
copy took to time out.

Both of those were real defects and both are fixed here. NEITHER was the cause of
the regression, which was found by bisecting the RavenDb suite instead:

  clean origin/main   1 of 28 failures (the known flake)
  37b45d8 fan-out   28 of 28 PASS
  527260f decoupling  7 failures

and by reading the failure dump rather than theorising. It shows the database
holding a clean 3/3/3/3 while the runtimes disagree -- fake://six/ and
fake://nine/ each RUNNING ON TWO NODES. Once the evaluation is decoupled from
execution, a dispatched-but-unconfirmed agent looks entirely unassigned, because
Agent.OriginalNode comes from persisted ActiveAgents and the assignment row only
appears once the agent is actually running. A later pass therefore emits a plain
AssignAgent to a different node with NO stop for the copy already starting
elsewhere. Before the decoupling the wave always completed first, so a move was a
ReassignAgent, which is stop-then-start. pinPendingAssignments only guards while
the ledger entry lives, and that TTL is 2x CheckAssignmentPeriod -- two seconds in
that suite, far shorter than confirmation takes.

The fix is a design change, not another patch: pending assignment has to be a
first-class grid state rather than a TTL'd suppression list. Either hold the
ledger entry until confirmed-or-failed (the dispatcher now knows about failure),
or convert an AssignAgent whose agent has a pending entry on a different node into
a ReassignAgent from that node so a stop is always issued. Probably both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(agents): pass the test cancellation token in the new agent drain tests

GH-3702 reinstated the xUnit1051 analyzer on main after this branch was cut, and CI
builds the merge commit, so these six WaitAsync calls and one Task.Delay became hard
build errors the moment #3720 landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ojaHNzLcE8m5krwfTNPxw

* fix(agents): make a pending assignment first-class grid state (GH-3698)

Decoupling the leader's assignment evaluation from the command drain let the same
agent run on two nodes at once. An agent dispatched but not yet confirmed running
looks completely unplaced -- Agent.OriginalNode comes from each node's PERSISTED
active agents, and the assignment row only appears once the agent is actually
running -- so a later pass re-decided its placement and emitted a bare AssignAgent
to a second node with no stop for the copy already starting on the first. Before the
decoupling the wave always finished first, so a move was a ReassignAgent.

The ledger that was meant to prevent this was a TTL'd suppression list applied to
commands after the fact. Two seconds in the compliance suite, and keyed on
(agent -> node), so a re-target defeated it entirely.

Pending assignment is now a state the grid can see:

- AssignmentGrid.Agent.PendingNode, projected from the ledger before each family
  distributes. TryBuildAssignmentCommand accounts for the possible copy in every
  arm: a stop aimed at the pending node when the agent is paused or detached, a
  stop-then-start when it moves, nothing when it is still going to the same place.
  Pausing a pending agent previously emitted nothing at all -- there was no
  OriginalNode to stop it on -- so the in-flight start came up and kept running.
- ReassignAgent.StopInSourceLane runs the stop half in the source node's lane, so it
  queues behind the start it is cancelling instead of racing ahead of it, finding
  nothing to stop, and letting that node bring the agent up moments later.
- The hold now lasts until the dispatch is confirmed or failed rather than for a
  fixed TTL. AgentCommandDispatcher already tracks a command from the moment it is
  queued until its lane is done with it; TryFindPendingDestination exposes that, and
  it -- not the clock -- decides when a dispatch is over. The TTL survives only as a
  backstop for the window between the wire completing and the assignment row turning
  up in a later LoadNodeAgentState.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ojaHNzLcE8m5krwfTNPxw

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reinstate the xUnit1051 analyzer, suppressed for the xUnit v3 migration

1 participant