Skip to content

daemon: durable-capture recovery Promise.race drops a late successful reattach, orphaning the handle #2099

Closed

Description

@thymikee

Severity: correctness (resource leak). MED.

Problem

Durable-capture recovery races an acquisition against a deadline/cancellation. When the deadline (or cancellation) wins, a subsequent successful acquisition is dropped on the floor — its live handle (recording / log capture) is never disposed. Result: orphaned host handles after a recovery timeout.

Evidence (at 848c6ea733)

  • Promise.race([acquisition, deadline, cancellation]); the finally does void acquisition.catch(() => {}).
  • acquireRecoveryAuthority does dispose both handle and control when it throws (catch block disposes late authority). The gap is the resolved-but-lost branch: if acquisition resolves after the deadline already settled the race, the returned { control, reattached } is never consumed and never disposed. void acquisition.catch(() => {}) only swallows rejections; it does not dispose a success.

Design / fix

On the lost-race path, dispose a late success, not just swallow a late failure:

} finally {
  clearTimeout(timer);
  stopListeningForCancellation();
  // dispose a late-arriving success whose result no caller will consume
  void acquisition.then(
    (authority) => disposeLateAuthority(params, /* control + handle */ authority, 'late_race_cleanup_failed', deadlineOrCancelError),
    () => {},
  );
}

(Reuse the existing disposeLateAuthority helper; dispose both control and, if reattached.status === 'active', its handle — mirroring the throw path.)

Acceptance criteria

  • A test where acquisition resolves after the deadline fires asserts the late handle and control are disposed (spy on Symbol.asyncDispose / the onLateCleanupFailure seam).
  • The winning-path (acquisition wins) behavior is unchanged.

Effort / risk

S, MED. One finally branch + one test. No behavior change on the success path.

Activity

  1. thymikee commented on Aug 27, 2026

    @thymikee
    MemberAuthor

    Triage confirmed against current main d44df9b. Ready for an evidence-first agent slice. The lost-race cleanup must attach only after the deadline or cancellation wins so it never disposes the authority returned to the winning caller. Plant both late-success branches red: active reattach disposes handle then control, non-active disposes control only; cleanup failures report through the existing seam without replacing the primary timeout or cancellation.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions