{"record":{"id":"0545c1842ad8e899","repo":"paperclipai/paperclip","slug":"teams-destination-is-missing-its-conversation-iden","errorCode":null,"errorMessage":"Teams destination is missing its conversation identity","messagePattern":"Teams destination is missing its conversation identity","errorType":"validation","errorClass":"TeamsServiceUrlValidationError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1195,"sourceCode":"      serviceUrl: string,\n      http: unknown,\n      settings?: unknown,\n    ) => TeamsApiClientInternals;\n    const scopedApi = new ApiClient(\n      serviceUrl,\n      defaultApi.http,\n      defaultApi._apiClientSettings,\n    );\n    return await apiScope.run(scopedApi, operation);\n  };\n  const withThreadServiceUrl = async <T>(\n    threadId: string,\n    operation: () => Promise<T>,\n    requireRoute = false,\n  ): Promise<T> => {\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 persistedServiceUrl = await teams.chat\n      ?.getState()\n      .get(teamsConversationRouteStateKey(decoded.conversationId));\n    if (\n      requireRoute &&\n      persistedServiceUrl == null &&\n      decoded.serviceUrl == null\n    ) {\n      throw new TeamsServiceUrlValidationError(\n        \"Teams file destination is missing its verified route\",\n      );\n    }\n    return await withServiceUrl(\n      persistedServiceUrl ?? decoded.serviceUrl ?? defaultApi.serviceUrl,\n      operation,","sourceCodeStart":1177,"sourceCodeEnd":1213,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1177-L1213","documentation":"withThreadServiceUrl decodes a Paperclip thread id back into Teams coordinates and requires a non-empty conversationId, because all outbound calls must be routed to the conversation's persisted or decoded service URL. If decodeThreadId yields no conversation identity, the destination is unusable and TeamsServiceUrlValidationError is thrown.","triggerScenarios":"Calling any thread-scoped outbound operation (send, postActivity, file card send, etc.) with a threadId whose decoded form has no conversationId — e.g. a malformed/legacy thread id, a non-Teams id passed into a Teams path, or an empty string id.","commonSituations":"Storing thread ids from a different chat adapter and passing them to Teams egress; truncating or corrupting persisted thread ids in your own database; legacy thread-id formats that decodeThreadId cannot parse into a conversation id.","solutions":["Verify the threadId originates from the Teams adapter (teams.decodeThreadId should round-trip it) and is not empty.","Log the decoded object to inspect what decodeThreadId returns for your id format.","Migrate any persisted legacy ids so they carry a conversation identity.","Guard call sites: only invoke Teams outbound APIs with ids produced by the Teams adapter's encodeThreadId."],"exampleFix":"// before\nawait sendTeamsMessage(someOtherAdapterThreadId, text); // foreign id\n// after\nif (!threadId?.startsWith('teams:')) return;\nawait sendTeamsMessage(threadId, text); // Teams-adapter-issued id with conversationId","handlingStrategy":"validation","validationCode":"const decoded = teams.decodeThreadId(threadId);\nif (typeof decoded?.conversationId !== 'string' || !decoded.conversationId) {\n  throw new Error(`threadId ${threadId} has no Teams conversation identity`);\n}","typeGuard":"function isTeamsThreadId(threadId: string): boolean {\n  try { return Boolean(teams.decodeThreadId(threadId)?.conversationId); } catch { return false; }\n}","tryCatchPattern":"try {\n  await sendViaTeams(threadId, payload);\n} catch (err) {\n  if (err instanceof TeamsServiceUrlValidationError && err.message.includes('conversation identity')) {\n    logger.warn(`Skipping non-Teams or malformed threadId: ${threadId}`);\n  }\n  throw err;\n}","preventionTips":["Only pass thread ids produced by the Teams adapter's own encoding into Teams outbound calls.","Namespace persisted thread ids by adapter so foreign ids never reach Teams egress.","Add round-trip tests: encodeThreadId -> decodeThreadId must preserve conversationId."],"tags":["teams","thread-id","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}