angular/angular-cli · error · SynchronousDelegateExpectedException

Expected a synchronous delegate but got an asynchronous one.

Error message

Expected a synchronous delegate but got an asynchronous one.

What it means

SyncDelegateHost wraps a Host and guarantees every operation completes synchronously. In the constructor it checks the delegate host's capabilities.synchronous flag and throws SynchronousDelegateExpectedException if the underlying host can only return Observables that may resolve asynchronously. This protects callers who expect plain return values rather than Observables.

Source

Thrown at packages/angular_devkit/core/src/virtual-fs/host/sync.ts:34

  HostCapabilities,
  HostWatchEvent,
  HostWatchOptions,
  Stats,
} from './interface';

export class SynchronousDelegateExpectedException extends BaseException {
  constructor() {
    super(`Expected a synchronous delegate but got an asynchronous one.`);
  }
}

/**
 * Implement a synchronous-only host interface (remove the Observable parts).
 */
export class SyncDelegateHost<T extends object = {}> {
  constructor(protected _delegate: Host<T>) {
    if (!_delegate.capabilities.synchronous) {
      throw new SynchronousDelegateExpectedException();
    }
  }

  protected _doSyncCall<ResultT>(observable: Observable<ResultT>): ResultT {
    let completed = false;
    let result: ResultT | undefined = undefined;
    let errorResult: Error | undefined = undefined;
    // Perf note: this is not using an observer object to avoid a performance penalty in RxJS.
    // See https://github.com/ReactiveX/rxjs/pull/5646 for details.
    observable.subscribe(
      (x: ResultT) => (result = x),
      (err: Error) => (errorResult = err),
      () => (completed = true),
    );

    if (errorResult !== undefined) {
      throw errorResult;
    }

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Use a host with capabilities.synchronous === true (e.g. CordHost backed by a sync host, or a host explicitly implementing the sync capability)
  2. Implement capabilities.synchronous: true on your custom Host, ensuring all its Observable-returning methods actually emit synchronously (of(), not from(promise))
  3. Use the async host directly and subscribe to the Observables instead of wrapping it in SyncDelegateHost

Example fix

// before
const host = new SyncDelegateHost(new SimpleMemoryHost());
// after
const syncHost = new SimpleMemoryHost();
syncHost.capabilities.synchronous = true; // only if reads are truly sync
const host = new SyncDelegateHost(syncHost);
Defensive patterns

Strategy: validation

Validate before calling

import { SyncDelegateHost } from '@angular-devkit/core/src/virtual-fs/host/sync';
function assertSyncHost(host) {
  if (!host.capabilities || host.capabilities.synchronous !== true) {
    throw new Error('Host must report capabilities.synchronous === true');
  }
  return host;
}
const host = new SyncDelegateHost(assertSyncHost(delegate));

Type guard

function isSyncCapable(host) {
  return typeof host === 'object' && host !== null &&
    host.capabilities?.synchronous === true;
}

Try / catch

try {
  const host = new SyncDelegateHost(delegate);
} catch (e) {
  if (e.constructor.name === 'SynchronousDelegateExpectedException') {
    // fall back to async Host API
  } else { throw e; }
}

Prevention

When it happens

Trigger: Constructing new SyncDelegateHost(delegate) with a host whose capabilities.synchronous is false or undefined — e.g. wrapping a plain Host<T> or SimpleMemoryHost instead of a sync-capable host.

Common situations: Passing the default async filesystem host to tooling that expects sync access (older Angular CLI schematics/utilities that used SyncDelegateHost), or forgetting to wrap a host in a sync adapter that reports synchronous: true.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/0e0ac70a75bd39f5. Report an issue: GitHub.