Skip to content

M2: Extensions DI lifetime shape — transient disposable sub-client hazards (R2-1) #31

Closed

Description

@Blind-Striker

From the second verified review of PR #26 (finding R2-1, CONFIRMED). Decision-first: the registration shape needs a maintainer seal before more client families pile onto it.

Finding. AddOpenCode registers the typed OpenCodeClient transient over the factory client, and SessionsClient as a transient resolving a hidden OpenCodeClient per resolution (OpenCodeServiceCollectionExtensions.cs:53). Three lifetime hazards follow:

  1. Root-provider retention — MS.Ext.DI tracks every transient IDisposable; a background service resolving from the root provider accumulates container-tracked OpenCodeClient instances until shutdown.
  2. Scope-disposal poisoning — a consumer storing a scope-resolved SessionsClient beyond its scope gets ObjectDisposedException on every later call; SessionsClient is not IDisposable, so nothing signals the constraint, and Pipeline.Dispose sets _disposed = true unconditionally even on the borrowed-transport path where it owns nothing.
  3. Split pipelines — injecting both OpenCodeClient and SessionsClient yields two unrelated pipelines; configuring/disposing one never affects the other.

Plus: the hand-grown one-line-per-family sub-client registration has no roster contract test, so the next generated client family silently lags DI until a consumer hits resolution failure.

Design space to weigh at the seal: scoped vs transient lifetimes; whether the borrowed-transport Pipeline.Dispose should no-op _disposed; resolving sub-clients from the same OpenCodeClient instance (e.g. via a scoped root); a generated or contract-tested registration roster (the Session 21 note already flags generator-emitted registrations as a trigger-based option).

Trigger: the first real DI consumer beyond the sandbox, or the next Extensions batch — whichever comes first. The hazards are live today but only bite consumers holding clients across scopes.

Milestone: M2 (the Extensions package grows in parallel with breadth; the shape must seal before more families multiply the registration list).

Activity

  1. Blind-Striker commented on Aug 15, 2026

    @Blind-Striker
    CollaboratorAuthor

    Superseded by the Q92 seal (research log Session 24, ADR-0010): the registration goes singleton-first without IHttpClientFactory, which resolves all three lifetime hazards by construction. This issue closes with the reshape PR that opens the M3 runway; the roster contract test carries over into that PR as this issue's one live remainder.

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

    needs-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions