paperclipai/paperclip · error · TeamsAdapterCompatibilityError

getUser is unavailable

Error message

getUser is unavailable

What it means

As part of the same compatibility audit, scopeMicrosoftTeamsEgress requires the Teams adapter to expose getUser, because it replaces it with a stub (async () => null) to keep pre-admission Teams events inert. If the method is absent the adapter is considered incompatible and the wrapper throws instead of proceeding with partially-stubbed behavior.

Source

Thrown at server/src/services/chat-sdk-runtime.ts:1028

): Adapter {
  const teams = adapter as unknown as TeamsAdapterInternals;
  if (!teams.app?.api) {
    throw new TeamsAdapterCompatibilityError("app.api is unavailable");
  }
  if (typeof teams.decodeThreadId !== "function") {
    throw new TeamsAdapterCompatibilityError("decodeThreadId is unavailable");
  }
  if (typeof teams.openDM !== "function") {
    throw new TeamsAdapterCompatibilityError("openDM is unavailable");
  }
  if (typeof teams.cacheUserContext !== "function") {
    throw new TeamsAdapterCompatibilityError("cacheUserContext is unavailable");
  }
  if (typeof teams.getIncomingUser !== "function") {
    throw new TeamsAdapterCompatibilityError("getIncomingUser is unavailable");
  }
  if (typeof teams.getUser !== "function") {
    throw new TeamsAdapterCompatibilityError("getUser is unavailable");
  }
  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {
    if (typeof teams[methodName] !== "function") {
      throw new TeamsAdapterCompatibilityError(`${methodName} is unavailable`);
    }
  }
  if (
    typeof teams.app.api.constructor !== "function" ||
    !("http" in teams.app.api)
  ) {
    throw new TeamsAdapterCompatibilityError(
      "the API client constructor or HTTP transport is unavailable",
    );
  }
  const apiDescriptor = Object.getOwnPropertyDescriptor(teams.app, "api");
  if (apiDescriptor && apiDescriptor.configurable === false) {
    throw new TeamsAdapterCompatibilityError(
      "app.api cannot be scoped per asynchronous conversation",

View on GitHub (pinned to 01ad858492)

Solutions

  1. Confirm typeof adapter.getUser === 'function' before calling scopeMicrosoftTeamsEgress.
  2. Restore or upgrade the official Teams adapter so getUser exists.
  3. In tests, provide a getUser stub (async () => null is sufficient) on the mock adapter.
  4. Check that subclassing or proxying the adapter has not shadowed or removed the method.

Example fix

// before
const mock = { app, decodeThreadId, openDM };
scopeMicrosoftTeamsEgress(mock as unknown as Adapter);
// after
const mock = { app, decodeThreadId, openDM, getUser: async () => null, getIncomingUser: async () => null, cacheUserContext: () => {} };
scopeMicrosoftTeamsEgress(mock as unknown as Adapter);
Defensive patterns

Strategy: validation

Validate before calling

if (typeof adapter.getUser !== 'function') {
  throw new Error('Teams adapter missing getUser; incompatible adapter version');
}

Type guard

function hasGetUser(a: unknown): a is { getUser: (...args: unknown[]) => Promise<unknown> } {
  return typeof a === 'object' && a !== null && typeof (a as any).getUser === 'function';
}

Try / catch

try {
  scoped = scopeMicrosoftTeamsEgress(adapter);
} catch (err) {
  if (err instanceof TeamsAdapterCompatibilityError && err.message === 'getUser is unavailable') {
    logger.error('Adapter lacks getUser; expected by the egress scoper');
  }
  throw err;
}

Prevention

When it happens

Trigger: Invoking scopeMicrosoftTeamsEgress on an adapter object lacking a callable getUser — wrong adapter type, older/newer adapter package where the method was renamed, or an incomplete mock in tests.

Common situations: Adapter package drift after a dependency upgrade; a developer-built adapter subclass that overrides the prototype and drops getUser; passing a generic chat adapter into the Teams-specific egress scoper.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/062cf2aeade387cb. Report an issue: GitHub.