Open source / Asynchronous state

Awaiting the full Workspace Trust transition

A public promise resolved before transition participants completed and before the trust-change event fired. One missing await weakened the lifecycle contract.

Project
Visual Studio Code
Contribution
PR #328626
Related issue
Issue #328625
Role
PR author
Outcome
Merged 19 August 2026

The method changed trust before its promise described completion.

Visual Studio Code’s Workspace Trust service coordinates a state transition with registered asynchronous participants and emits an onDidChangeTrust event when the transition has completed. Callers can await setWorkspaceTrust() or the underlying setUrisTrust() API to sequence later work after that lifecycle.

Issue #328625 identified that the returned promise could settle too early. PR #328626, authored by Zain Nadeem, restored the completion contract and added a deterministic regression test using a deliberately paused transition participant.

Awaiting the public API did not await its asynchronous participants.

A caller could execute await setWorkspaceTrust(true) and resume while a transition participant was still running. At that point, the internal trusted value had changed, but participant work had not completed and onDidChangeTrust had not yet fired.

Observed orderstate changes → caller resumes → participant completes → event
Required orderstate changes → participant completes → event → caller resumes

This is a lifecycle consistency problem rather than a claim that the trust decision itself was bypassed. The upstream PR does not classify the bug as a vulnerability.

An async method invoked a promise-returning operation without awaiting it.

setUrisTrust() awaited canonicalization of its URI list, then called doSetUrisTrust(...) without awaiting or returning that operation’s promise. Because the outer function was itself async, it resolved after starting the transition rather than after the transition chain finished.

async setUrisTrust(uris, trusted) {
    this.doSetUrisTrust(await canonicalize(uris), trusted);
    // The returned promise is discarded.
}

The defect was only one missing keyword, but the semantic difference covered participant execution, event ordering, and every caller relying on the method’s completion.

Propagate completion through the existing promise chain.

The core change adds await to the doSetUrisTrust() call. No new transition mechanism is introduced; the public method now reflects the lifecycle already implemented by the lower-level operation.

async setUrisTrust(uris, trusted) {
    await this.doSetUrisTrust(await canonicalize(uris), trusted);
}

The PR also adjusts Workspace Trust editor deletion focus handling so the UI selects a stable neighboring row before awaiting the trust mutation, then fires the delete event after the asynchronous operation completes.

Control the participant instead of relying on timing.

The regression registers a Workspace Trust transition participant whose completion is gated by a manually released promise. It also records whether the participant started, whether it completed, whether the trust-change event fired, and whether the public promise resolved.

PhaseExpected state
Before transitionNo participant, event, or resolution
Participant pausedTrust changed; public promise unresolved; event not fired
Participant releasedParticipant completes and event fires
Public await completesFull transition is finished

This structure avoids a sleep-based race. The test advances only when each observable phase is established. The PR record documents compilation, the focused regression, the broader Workspace Trust test selection, and whitespace validation.

The completion contract and UI sequencing were merged together.

Zain authored PR #328626. The contribution changed the trust service, the Workspace Trust editor’s deletion flow, and the service tests. It was merged on 19 August 2026 as commit 773e6102d24184f4f9eaee9482d25cb85a6d6514.

The article deliberately does not claim release inclusion or broader security impact because neither is needed to describe the verified upstream change.

An async API's promise is part of its public state model.

  • Calling an asynchronous operation from an async method does not propagate completion unless its promise is awaited or returned.
  • Lifecycle tests should assert event ordering and participant completion, not merely the final boolean state.
  • Controllable promises produce deterministic concurrency tests without arbitrary delays.
  • UI state changes around asynchronous mutations should select and restore focus deliberately.

Authoritative upstream record.