{"record":{"id":"11153712203974cf","repo":"paperclipai/paperclip","slug":"durable-route-state-is-unavailable","errorCode":null,"errorMessage":"durable route state is unavailable","messagePattern":"durable route state is unavailable","errorType":"exception","errorClass":"TeamsAdapterCompatibilityError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1298,"sourceCode":"  }\n\n  teams.paperclipRecordThreadServiceUrl = async (\n    threadId: string,\n    serviceUrlValue: unknown,\n  ) => {\n    const decoded = teams.decodeThreadId!(threadId);\n    if (typeof decoded.conversationId !== \"string\" || !decoded.conversationId) {\n      throw new TeamsServiceUrlValidationError(\n        \"Teams destination is missing its conversation identity\",\n      );\n    }\n    const serviceUrl = trustedTeamsServiceUrl(\n      serviceUrlValue,\n      trustedConfiguredApiUrl,\n    );\n    const state = teams.chat?.getState();\n    if (!state) {\n      throw new TeamsAdapterCompatibilityError(\n        \"durable route state is unavailable\",\n      );\n    }\n    await state.set(\n      teamsConversationRouteStateKey(decoded.conversationId),\n      serviceUrl,\n    );\n  };\n\n  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {\n    const original = teams[methodName];\n    if (typeof original !== \"function\") continue;\n    teams[methodName] = (threadId: string, ...args: unknown[]) =>\n      withThreadServiceUrl(threadId, async () =>\n        Reflect.apply(original, teams, [threadId, ...args]),\n      );\n  }\n  const originalOpenDM = teams.openDM;","sourceCodeStart":1280,"sourceCodeEnd":1316,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1280-L1316","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Configure durable state storage for the Teams adapter (e.g. blob or file-backed state) so teams.chat.getState() returns a store.","Confirm the adapter was initialized (chat.initialize()) before recording routes.","Check adapter construction options: ensure the Chat state plugin/middleware is enabled for microsoft-teams.","If durability is intentionally disabled, bypass paperclipRecordThreadServiceUrl and rely on serviceUrl embedded in incoming activities."],"exampleFix":"// before\nnew TeamsAdapter({ /* no state configured */ });\n// after\nnew TeamsAdapter({ chat: { state: new FileStateStore(\"./data/teams-state\") } });","handlingStrategy":"validation","validationCode":"const state = teams.chat?.getState();\nif (!state) throw new Error(\"configure durable state storage before recording Teams routes\");","typeGuard":"null","tryCatchPattern":"try {\n  await teams.paperclipRecordThreadServiceUrl(threadId, serviceUrl);\n} catch (err) {\n  if (err instanceof TeamsAdapterCompatibilityError && /route state/.test(err.message)) {\n    logger.error(\"Teams durable state store not configured; routes will not persist\");\n    return; // fall back to serviceUrl from incoming activities\n  }\n  throw err;\n}","preventionTips":["Always configure a state store (blob/file/memory) for the Teams adapter in production.","Await chat.initialize() before any route persistence.","Add a startup smoke check that getState() returns a store."],"tags":["teams","state","configuration"],"backgroundTag":"missing-required-config","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"}