{"record":{"id":"20e0799c4556c6ca","repo":"paperclipai/paperclip","slug":"durable-route-recorder-is-unavailable","errorCode":null,"errorMessage":"durable route recorder is unavailable","messagePattern":"durable route recorder is unavailable","errorType":"exception","errorClass":"TeamsAdapterCompatibilityError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":2546,"sourceCode":"  }\n\n  /**\n   * Persist a Teams activity's authenticated reply route only after the\n   * control plane has accepted the callback under the current runtime and\n   * credential generation. The route is intentionally separate from the\n   * durable provider thread id because Microsoft can move a conversation\n   * between regional Bot Connector service URLs.\n   */\n  async recordMicrosoftTeamsRoute(\n    threadId: string,\n    serviceUrl: unknown,\n    raw?: unknown,\n  ): Promise<void> {\n    if (this.provider !== \"microsoft-teams\") return;\n    const teams = this.adapter as unknown as TeamsAdapterInternals;\n    const recorder = teams.paperclipRecordThreadServiceUrl;\n    if (typeof recorder !== \"function\") {\n      throw new TeamsAdapterCompatibilityError(\n        \"durable route recorder is unavailable\",\n      );\n    }\n    await recorder.call(this.adapter, threadId, serviceUrl);\n    if (raw !== undefined) {\n      const acceptedActivityRecorder = teams.paperclipRecordAcceptedActivity;\n      if (typeof acceptedActivityRecorder !== \"function\") {\n        throw new TeamsAdapterCompatibilityError(\n          \"durable accepted-activity recorder is unavailable\",\n        );\n      }\n      await acceptedActivityRecorder.call(this.adapter, raw);\n    }\n  }\n\n  async sendTeamsFileConsentCard(\n    threadId: string,\n    card: ReturnType<typeof buildTeamsFileConsentCard>,","sourceCodeStart":2528,"sourceCodeEnd":2564,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L2528-L2564","documentation":"recordMicrosoftTeamsRoute persists the authenticated Bot Connector serviceUrl for a Teams thread using the adapter's internal paperclipRecordThreadServiceUrl hook. For non-Teams providers it is a no-op (early return), but when the provider IS microsoft-teams and the adapter lacks the recorder function, the runtime cannot durably store the route and throws a TeamsAdapterCompatibilityError.","triggerScenarios":"A Teams activity callback invoking recordMicrosoftTeamsRoute(threadId, serviceUrl, raw) where the wired microsoft-teams adapter does not define paperclipRecordThreadServiceUrl — e.g. an older adapter build or a minimal test adapter.","commonSituations":"Teams adapter version pinned before the durable-routing feature; custom or stub Teams adapters missing paperclip* internals; dependency upgrade that swapped the adapter implementation without updating the runtime expectations.","solutions":["Upgrade the microsoft-teams adapter so it implements paperclipRecordThreadServiceUrl.","Feature-detect the recorder before calling and skip durable route persistence (log a warning) when absent.","Align adapter and chat-sdk-runtime versions so the internal contract matches."],"exampleFix":"// before\nawait runtime.recordMicrosoftTeamsRoute(threadId, serviceUrl, raw);\n\n// after\nconst teams = runtime.getProviderAdapter() as {\n  paperclipRecordThreadServiceUrl?: unknown;\n};\nif (typeof teams.paperclipRecordThreadServiceUrl === \"function\") {\n  await runtime.recordMicrosoftTeamsRoute(threadId, serviceUrl, raw);\n} else {\n  console.warn(\"teams durable route recorder unavailable; skipping persistence\");\n}","handlingStrategy":"type-guard","validationCode":"const teams = runtime.getProviderAdapter() as Record<string, unknown>;\nif (runtime.provider === \"microsoft-teams\" && typeof teams.paperclipRecordThreadServiceUrl !== \"function\") {\n  console.warn(\"teams adapter lacks durable route recorder\");\n}","typeGuard":"function supportsRouteRecording(a: unknown): a is { paperclipRecordThreadServiceUrl: (threadId: string, serviceUrl: unknown) => Promise<void> } {\n  return typeof a === \"object\" && a !== null && typeof (a as any).paperclipRecordThreadServiceUrl === \"function\";\n}","tryCatchPattern":"try {\n  await runtime.recordMicrosoftTeamsRoute(threadId, serviceUrl, raw);\n} catch (err) {\n  if (err instanceof TeamsAdapterCompatibilityError && err.message.includes(\"route recorder is unavailable\")) {\n    console.warn(\"skipping durable route persistence\");\n  } else throw err;\n}","preventionTips":["Keep the microsoft-teams adapter and chat-sdk-runtime versions aligned.","Feature-detect paperclipRecordThreadServiceUrl at adapter startup and log missing capabilities once.","Add adapter contract tests covering the paperclip* internal hooks."],"tags":["microsoft-teams","adapter-compatibility","routing","missing-capability"],"backgroundTag":"method-not-implemented","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}