Skip to content

IEventSubscriptionAgentFamily.FindAgentUriAsync resolves only live agents — can't restart/rebuild a non-running projection #3124

Description

@jeremydmiller

Summary

IEventSubscriptionAgentFamily.FindAgentUriAsync(shardIdentity, tenantId, ct) returns null when there is no live (running) agent for the shard. That makes it impossible to resolve — and therefore pause/restart/rebuild — a projection whose agent is registered but not currently running (paused, stopped, crashed, or in the startup window before agents start).

Why it matters

CritterWatch's admin commands (PauseProjectionHandler, RestartProjectionHandler, RebuildProjectionHandler) all resolve the real agent URI via FindAgentUriAsync (they must not hand-compose the URI). When an operator tries to recover a stopped/orphaned async projection — exactly the situation where Restart/Rebuild is most needed — FindAgentUriAsync returns null and the command no-ops (Restart/Rebuild return a "No live agent" result; Pause silently returns).

This also appears to be the root of a flaky pause→restart round-trip: a paused agent may no longer count as "live", so the subsequent restart can't find it to clear the pause restriction.

Reported downstream as CritterWatch ProductSupport #18/#19/#20 (projection Pause/Restart/Rebuild "do nothing" on an Orphaned/Continuous projection) and tracked alongside CritterWatch's own flaky can_pause_and_restart_a_projection integration test.

Ask

Provide a way to resolve the agent URI for a registered subscription/projection shard regardless of its current run state — e.g. FindAgentUriAsync consulting the registered subscription set (not just live agents), or a sibling FindRegisteredAgentUriAsync. That would let an operator restart/rebuild a projection that isn't currently running, which is the whole point of those operations.

Repro sketch

  • Register an async projection; let it create a progression row.
  • Stop/pause its agent (or query during the pre-start window).
  • Call FindAgentUriAsync(shardIdentity, null, ct) → returns null though the projection is registered.

Activity

  1. added a commit that references this issue on Jun 17, 2026
  2. jeremydmiller commented on Jun 17, 2026

    @jeremydmiller
    MemberAuthor

    The core of this is delivered and merged in #3126.

    IEventSubscriptionAgentFamily.FindAgentUriAsync resolves from the registered subscription set (usage.Subscriptions) rather than only live agents, so a paused/stopped/crashed/not-yet-started projection still resolves — which is what makes restart/rebuild of a non-running projection possible. #3126 also:

    • corrected the interface + implementation XML docs (they previously said "live agent" — the exact contradiction reported here), and
    • added regression tests proving a registered shard resolves with no running agent, and that an unregistered shard returns null.

    Covered: the non-tenant case (the repro here, FindAgentUriAsync("Trip:All", null)), the pre-start window, and single-database per-tenant partitioning (tenant in the shard RelativeUrl).

    One scoped gap remains, split out as a focused follow-up: #3128 — under database-per-tenant (sharded databases) a non-null tenantId can't be resolved, because that needs a tenantId → databaseId mapping rather than matching the shard RelativeUrl. That isn't part of this issue's repro (which uses a null tenant), so it's tracked separately.

    Recommend closing this in favor of #3128 for the remaining database-per-tenant work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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