Recovery hint attributes: declare which failure recovers how - #597
Merged
Conversation
A tag or trait says *this test* is unreliable. A hint says *which failure* is
unreliable and what fixes it, so an assertion failure on the same test is still
reported as the bug it is rather than retried away with everything else.
[ClearsOnRetry(typeof(TimeoutException), Because = "the broker warms up slowly")]
[ClearsOnRecycle("rabbit,kafka", typeof(BrokerUnavailableException))]
[ClearsInFreshProcess(typeof(BadImageFormatException))]
[NeverRecovers(typeof(SomeDeterministicBug), Because = "this is a real bug")]
public class OrderTests;
These live in JasperFx rather than in a test runner on purpose. Any suite already
referencing JasperFx -- directly, or transitively through Marten, Wolverine or
Polecat -- can now write down what it knows about its own flakiness without
taking a dependency on whatever ends up running it. A runner that understands
them acts on them; a runner that does not is unaffected, because these carry no
behaviour and start nothing.
DispositionKind moves here with them rather than a runner keeping its own copy
mapped at a seam. Two enums meaning the same thing is how a vocabulary drifts.
NeverRecovers is what makes the set usable rather than merely expressive. Without
it the only way to stop a broad "retry three times" policy re-running a
deterministic bug is to remove the retry, which also stops the retries that were
pulling their weight.
Deliberately NOT included: anything that reads or acts on a hint. Matching a
failure to a hint, resolving assembly < class < method scope, and deciding what a
budget permits are runner policy and stay in the runner. Note in particular that
a hint must never widen a retry budget -- what recovers is the author's
knowledge, how much time a run may spend is the operator's, and conflating them
would let a test author escape a ceiling set by whoever runs the suite.
The attributes are declarations only; there is as yet no in-box reader. An
out-of-process runner cannot see attributes at all without either projecting them
as traits or emitting a build-time manifest, and that choice belongs with the
runner that needs it.
12 new tests; 491 pass in CoreTests. No new AOT/trim warnings -- attributes are
declarations, so nothing here reflects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vmmUkRtSFMk1Pbf9E5eor
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A tag or trait says this test is unreliable. A hint says which failure is unreliable and what fixes it — so an assertion failure on the same test is still reported as the bug it is, rather than retried away with everything else.
Why here rather than in a test runner
Any suite that would want these already references JasperFx — directly, or transitively through Marten, Wolverine or Polecat. Putting the vocabulary here means a project can write down what it knows about its own flakiness without taking a dependency on whatever ends up running it. A runner that understands them acts on them; a runner that does not is unaffected, because these carry no behaviour and start nothing.
DispositionKindmoves here with them rather than a runner keeping its own copy mapped at a seam. Two enums meaning the same thing is how a vocabulary starts drifting.NeverRecoversis what makes the set usable rather than merely expressive. Without it, the only way to stop a broad "retry three times" policy from re-running a deterministic bug is to remove the retry — which also stops the retries that were pulling their weight.Deliberately not included
Anything that reads or acts on a hint. Matching a failure to a hint, resolving assembly < class < method scope, and deciding what a retry budget permits are runner policy and stay in the runner.
One rule worth stating for whoever implements that side: a hint must never widen a retry budget. What recovers is the author's knowledge; how much time a run may spend is the operator's. Conflating them lets a test author escape a ceiling set by whoever runs the suite.
Also worth being explicit: these are declarations, and there is no in-box reader yet. An out-of-process runner cannot see attributes at all without either projecting them as traits or emitting a build-time manifest — and that choice belongs with the runner that needs it, not here.
Verification
12 new tests covering the disposition each hint declares, resource parsing (splitting, trimming, empty-entry compaction), and discovery from class, method and assembly scope. 491 pass in CoreTests, 1 pre-existing skip.
No new AOT or trim warnings — attributes are declarations, so nothing here reflects.
🤖 Generated with Claude Code
https://claude.ai/code/session_012vmmUkRtSFMk1Pbf9E5eor