Skip to content

CIPolecat red on main since 1dfdd42a1: bisected to a version-string bump, mechanism unknown #3776

Description

@jeremydmiller

CIPolecat has been red on main since 1dfdd42a1. Bisected to a commit whose entire content is a version-string bump, which cannot be the mechanism — so something reads $(Version) at a distance and I could not find what.

Status: cause localised with high confidence, mechanism unknown. Nothing fixed.

The failure

18 tests fail, all with the same exception, clustered in two namespaces:

[FAILED] PolecatTests.Requirements.using_data_requirements.document_does_not_exist_attribute_happy_path
  — Wolverine.Runtime.Routing.IndeterminateRoutesException : Could not determine any valid
    subscribers or local handlers for message type PolecatTests.Requirements.EnsureNoDuplicatePcThing
failing namespace count
PolecatTests.Requirements.using_data_requirements 28 log lines (~18 unique)
PolecatTests.strong_typed_identifiers 4
PolecatTests.handler_actions_with_returned_StartStream 2
PolecatTests.handler_actions_with_implied_polecat_operations 2

Exception breakdown across the run: 54 IndeterminateRoutesException, 24 RequiredDataMissingException, 24 ShouldAssertException, 1 InvalidOperationException. The supervisor reports
43 passed, 25 passed on retry, 18 failed (25 retries, 27 worker processes) — identically on every red run.

This is a handler-discovery failure: the message types have no chain, so InvokeAsync cannot route them.

The bisect

Six runs, each a workflow_dispatch on a throwaway branch with the tests.yml matrix trimmed to CIPolecat only (one job per run instead of 28). All within the same ~40 minute window, so runner-image and upstream-package drift are held constant.

commit content result runs
7e8f8ba86 GH-3774 — completion continuations honor the metrics-silent tracker pass 30726582635
57464ba91 GH-3773 — RavenDb/CosmosDb dead-letter readers pass ×3 30727034697, 30727226425, 30727230786
1dfdd42a1 Bump to 6.24.4 fail ×3 30726791466 + organic 30724675101 (main) and 30725560867 (PR #3775)

git diff between the passing and failing probe branches is one line:

-        <Version>6.24.3</Version>
+        <Version>6.24.4</Version>

Under a coin-flip null, 3/3 vs 3/3 is p ≈ 1.6%; counting the two organic failures it is 5/5 red at the bumped version. Bounded on the other side by 11 consecutive green CIPolecat runs on main through 3ad7986b6.

Ruled out (tested, not assumed)

Does not reproduce locally

Green in every local configuration tried, on macOS/arm64 against the docker-compose SQL Server:

  • the failing class alone — 14/14
  • the full CIPolecat shard filter in one process — 86/86
  • the real supervisor via ./build.sh CIPolecat --disable-test-retry — 86/86
  • all of the above at 1dfdd42a1, the known-red sha
  • and again with Polecat pinned to 5.9.1

Leads worth taking first

  1. Something reads $(Version) at runtime. That is the whole puzzle. Both failing areas are source-generator territory (JasperFx.Events.SourceGenerator, StronglyTypedId — note strong_typed_identifiers is among the failures), and Wolverine's runtime codegen is the obvious place an assembly version could leak into type resolution. Start by diffing the generated code between the two versions rather than the source.
  2. Why only in CI. The local/CI gap is real and is worth closing on its own (see below) — whatever differs there is likely the same thing that makes the version visible.

Adjacent finding worth fixing regardless

Polecat is the only floating dependency in Directory.Packages.props:

<PackageVersion Include="Polecat" Version="[5.7.0,6.0.0)" />

A warm local cache resolves 5.7.0; CI restores fresh and gets 5.9.1. That is a silent two-minor-version gap between what a developer tests and what CI tests. It is not the cause here — pinning 5.9.1 locally still passes — but it will eventually produce a "works on my machine" that costs someone a day.

Things that will bite you

  • CIPolecat is one of three shards. PolecatTests is split by namespace across CIPolecat / CIPolecatWorkflow / CIPolecatSagas (CI: CIAWS and CIPolecat jobs chronically hit the 20-minute execution timeout #3350); the failing namespaces live in the CIPolecat catch-all. Running the whole project locally is not the same test.
  • The identical 43/25/18 on every red run is not by itself evidence of determinism. With 2 worker processes and a deterministic class partition, any whole-worker failure yields the same split. It took a controlled pass/fail pair at adjacent commits to establish determinism.
  • Trimming the tests.yml matrix to a single target turns a bisect probe from 28 jobs into 1. Cheap enough to resample properly, which is what separated signal from luck here.

Reproduction harness

Three throwaway branches carry the trimmed-matrix probes, one per bisect point. The repo ruleset blocks branch deletion, so they are still there and are directly re-runnable with gh workflow run tests.yml --ref <branch>:

  • tmp/bisect-gh3774 — at 7e8f8ba86 (green)
  • tmp/bisect-mid — at 57464ba91 (green)
  • tmp/bisect-tip — at 1dfdd42a1 (red)

They should be deleted once this is closed.

🤖 Generated with Claude Code

https://claude.ai/code/session_013eR4GL278688VhyhrGcttJ

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions