paperclipai/paperclip · error · TeamsAdapterCompatibilityError

durable route state is unavailable

Error message

durable route state is unavailable

What it means

To durably persist a Teams conversation route, the runtime needs the adapter's chat state store (teams.chat.getState()). If the state client is not available — because durable state storage was never configured for the Teams adapter — this TeamsAdapterCompatibilityError is thrown instead of silently dropping the route.

Source

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

  }

  teams.paperclipRecordThreadServiceUrl = async (
    threadId: string,
    serviceUrlValue: unknown,
  ) => {
    const decoded = teams.decodeThreadId!(threadId);
    if (typeof decoded.conversationId !== "string" || !decoded.conversationId) {
      throw new TeamsServiceUrlValidationError(
        "Teams destination is missing its conversation identity",
      );
    }
    const serviceUrl = trustedTeamsServiceUrl(
      serviceUrlValue,
      trustedConfiguredApiUrl,
    );
    const state = teams.chat?.getState();
    if (!state) {
      throw new TeamsAdapterCompatibilityError(
        "durable route state is unavailable",
      );
    }
    await state.set(
      teamsConversationRouteStateKey(decoded.conversationId),
      serviceUrl,
    );
  };

  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {
    const original = teams[methodName];
    if (typeof original !== "function") continue;
    teams[methodName] = (threadId: string, ...args: unknown[]) =>
      withThreadServiceUrl(threadId, async () =>
        Reflect.apply(original, teams, [threadId, ...args]),
      );
  }
  const originalOpenDM = teams.openDM;

View on GitHub (pinned to 01ad858492)

Solutions

  1. Configure durable state storage for the Teams adapter (e.g. blob or file-backed state) so teams.chat.getState() returns a store.
  2. Confirm the adapter was initialized (chat.initialize()) before recording routes.
  3. Check adapter construction options: ensure the Chat state plugin/middleware is enabled for microsoft-teams.
  4. If durability is intentionally disabled, bypass paperclipRecordThreadServiceUrl and rely on serviceUrl embedded in incoming activities.

Example fix

// before
new TeamsAdapter({ /* no state configured */ });
// after
new TeamsAdapter({ chat: { state: new FileStateStore("./data/teams-state") } });
Defensive patterns

Strategy: validation

Validate before calling

const state = teams.chat?.getState();
if (!state) throw new Error("configure durable state storage before recording Teams routes");

Type guard

null

Try / catch

try {
  await teams.paperclipRecordThreadServiceUrl(threadId, serviceUrl);
} catch (err) {
  if (err instanceof TeamsAdapterCompatibilityError && /route state/.test(err.message)) {
    logger.error("Teams durable state store not configured; routes will not persist");
    return; // fall back to serviceUrl from incoming activities
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling teams.paperclipRecordThreadServiceUrl when teams.chat is undefined or teams.chat.getState() returns null/undefined, i.e. the Teams adapter was constructed without a durable state store.

Common situations: Running without configured state storage (e.g. no blob/file/memory state configured in the bot adapter); production adapter built without Chat state middleware; misconfigured adapter options that disable state; SDK version where getState is lazily unavailable until initialize.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


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