Skip to content

Permission-bridge app-claim-first test races the server leg and times out on Windows #762

Closed

Description

@alexeyzimarev

Problem

LocalPermissionBridgeInteractiveTests.App_claim_first_answers_the_hook_cancels_the_server_await_responds_to_the_server_and_logs_app (test/Capacitor.Cli.Daemon.Tests.Unit/Services/LocalPermissionBridgeInteractiveTests.cs) fails intermittently on the windows-latest leg with a 30 s TimeoutException from awaitCts.Task.WaitAsync(TimeSpan.FromSeconds(30)): the fake server's AwaitScript is never invoked.

Seen on main itself, not only on PRs:

Run Ref Result
33786667527 main @ 72bdaa1 failed, 30.3 s
33787264200 PR #759 merge commit failed, 30.1 s; passed on re-run

Mechanism

The test settles from the app as soon as the broker shows the pending request, then waits for the server await to be cancelled. But LocalPermissionBridge.RunServerLegAsync checks settlement.IsCompleted right after BeginPermissionRequestAsync returns and, when the settlement already landed, relays it and returns without ever calling AwaitPermissionDecisionAsync. That is deliberate product behaviour. On a loaded runner the app claim wins that window, so the await the test is waiting to see cancelled never starts, and the test hangs until its own 30 s bound.

The premise in the name, "app claim first while the server await is in flight", is therefore not guaranteed by the test as written: it only holds when the server leg happens to reach AwaitPermissionDecisionAsync before the test's 10 ms pending-poll plus TrySettle.

Proposed fix

Make the premise explicit: have the test await awaitCts.Task (the AwaitScript invocation) before calling Broker.TrySettle, so the app claim provably lands while the server await is in flight. The rest of the assertions then hold deterministically. The sibling scenario, app claim landing before the server leg reaches the await, is covered by the settlement.IsCompleted relay path and could get its own test if it has none.

Activity

  1. linear-code commented on Sep 3, 2026

    @linear-code
  2. Inok commented on Sep 14, 2026

    @Inok
    Member

    Already fixed in the tree. LocalPermissionBridgeInteractiveTests.cs:75 awaits awaitCts.Task and only then calls TrySettle on line 76, which is the proposed fix verbatim — the app claim provably lands while the server await is in flight, so the premise in the test's name is now guaranteed rather than hoped for.

    It arrived as a drive-by in 84df292 (#812) rather than under this issue, which is why the issue stayed open.

    The sibling scenario the issue mentions — an app claim landing before the server leg reaches the await — is covered by App_claim_before_the_server_leg_awaits_is_relayed_without_an_await, so the settlement.IsCompleted relay path has a test of its own.

  3. Inok commented on Sep 14, 2026

    @Inok
    Member

    Correction to the last paragraph above: I named a sibling test that does not exist. The scenario the issue anticipated — an app claim landing before the server leg reaches AwaitPermissionDecisionAsync, taking the settlement.IsCompleted relay path — has no test of its own in LocalPermissionBridgeInteractiveTests.

    That gap is what the issue predicted, so it is worth its own issue rather than reopening this one: the fix here was making the in-flight premise deterministic, and that is done.

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