{"record":{"id":"61ee76603a23f4d6","repo":"paperclipai/paperclip","slug":"app-api-cannot-be-scoped-per-asynchronous-conversa","errorCode":null,"errorMessage":"app.api cannot be scoped per asynchronous conversation","messagePattern":"app\\.api cannot be scoped per asynchronous conversation","errorType":"exception","errorClass":"TeamsAdapterCompatibilityError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1045,"sourceCode":"  if (typeof teams.getUser !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"getUser is unavailable\");\n  }\n  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {\n    if (typeof teams[methodName] !== \"function\") {\n      throw new TeamsAdapterCompatibilityError(`${methodName} is unavailable`);\n    }\n  }\n  if (\n    typeof teams.app.api.constructor !== \"function\" ||\n    !(\"http\" in teams.app.api)\n  ) {\n    throw new TeamsAdapterCompatibilityError(\n      \"the API client constructor or HTTP transport is unavailable\",\n    );\n  }\n  const apiDescriptor = Object.getOwnPropertyDescriptor(teams.app, \"api\");\n  if (apiDescriptor && apiDescriptor.configurable === false) {\n    throw new TeamsAdapterCompatibilityError(\n      \"app.api cannot be scoped per asynchronous conversation\",\n    );\n  }\n  const trustedConfiguredApiUrl = configuredApiUrl\n    ? normalizedTeamsServiceUrl(configuredApiUrl)\n    : null;\n\n  // The pinned Teams adapter caches activity/user metadata and may query the\n  // members or Graph APIs before Chat dispatches to Paperclip's reach and\n  // tenant checks. Keep authenticated but unadmitted events observationally\n  // inert. Accepted activities explicitly persist the minimum routing context\n  // through paperclipRecordAcceptedActivity below.\n  teams.cacheUserContext = () => {};\n  teams.getIncomingUser = async () => null;\n  teams.getUser = async () => null;\n\n  teams.paperclipRecordAcceptedActivity = async (activityValue: unknown) => {\n    if (!isRecord(activityValue)) return;","sourceCodeStart":1027,"sourceCodeEnd":1063,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1027-L1063","documentation":"To scope egress, the wrapper redefines teams.app.api with a getter backed by AsyncLocalStorage so each conversation sees its own API client. If the app object's 'api' property is a non-configurable own property, Object.defineProperty would throw a TypeError, so the wrapper pre-empts it with this compatibility error: the adapter's app.api cannot be re-scoped per asynchronous conversation.","triggerScenarios":"The adapter (or test harness) defined app.api with Object.defineProperty(..., { configurable: false }) or froze/sealed the app object (Object.freeze(app)), making 'api' non-configurable.","commonSituations":"App objects frozen for immutability in tests; custom adapter code that locks down the api property; adapter versions that changed how app.api is exposed from a configurable accessor to a fixed value.","solutions":["Remove any Object.freeze()/Object.seal() on the Teams app object before passing it to scopeMicrosoftTeamsEgress.","If app.api was defined with defineProperty, set configurable: true.","Expose api as a normal property (this.api = client) rather than a locked own property.","Check adapter version notes: if the SDK made api non-configurable, upgrade Paperclip's wrapper or pin the older SDK."],"exampleFix":"// before\nconst app = { id: 'x' };\nObject.defineProperty(app, 'api', { value: client, configurable: false });\n// after\nObject.defineProperty(app, 'api', { value: client, writable: true, configurable: true, enumerable: true });","handlingStrategy":"validation","validationCode":"const d = Object.getOwnPropertyDescriptor(adapter.app, 'api');\nif (d && d.configurable === false) {\n  throw new Error('app.api must be configurable to be scoped per conversation');\n}","typeGuard":null,"tryCatchPattern":"try {\n  scoped = scopeMicrosoftTeamsEgress(adapter);\n} catch (err) {\n  if (err instanceof TeamsAdapterCompatibilityError && err.message.includes('cannot be scoped')) {\n    logger.error('app.api is non-configurable (frozen/sealed app?) — unfreeze before scoping');\n  }\n  throw err;\n}","preventionTips":["Do not Object.freeze/Object.seal the Teams app object passed into scopeMicrosoftTeamsEgress.","Define api with configurable: true if you must use defineProperty.","Document that the wrapper owns the app.api property for the process lifetime."],"tags":["teams","adapter-compatibility","property-descriptor"],"backgroundTag":"incompatible-source-type","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"}