{"record":{"id":"42d5bc8fff840c1b","repo":"paperclipai/paperclip","slug":"teams-file-card-send-receipt-is-unproven","errorCode":null,"errorMessage":"Teams file-card send receipt is unproven","messagePattern":"Teams file-card send receipt is unproven","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1273,"sourceCode":"            // ALS as ordinary thread operations. Thread.post would turn these\n            // provider-native cards into AdaptiveCards.\n            result = await teams.app!.send!(decoded.conversationId as string, {\n              type: \"message\",\n              attachments: [card],\n            });\n          } catch {\n            // Provider errors may contain private card contexts. No implicit\n            // retry: an uncertain POST remains caller-owned durable evidence.\n            throw new Error(\"Teams file-card send result is unknown\");\n          }\n          if (\n            !isRecord(result) ||\n            typeof result.id !== \"string\" ||\n            !result.id ||\n            result.id.length > 1024 ||\n            /[\\x00-\\x20\\x7f]/.test(result.id)\n          ) {\n            throw new Error(\"Teams file-card send receipt is unproven\");\n          }\n          return { id: result.id };\n        },\n        true,\n      );\n    };\n  }\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    }","sourceCodeStart":1255,"sourceCodeEnd":1291,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1255-L1291","documentation":"After a successful teams.app.send call, the returned receipt is validated: it must be a record with a non-empty 'id' string of at most 1024 chars containing no control characters. If the provider returns a response that lacks a usable message id, this error is thrown — the send may have succeeded, but no durable proof of delivery exists.","triggerScenarios":"teams.app.send resolves but the result is not a record, result.id is missing/not a string/empty, exceeds 1024 chars, or contains control characters.","commonSituations":"Bot Framework SDK version returning a differently shaped response; adapter mock or test double returning undefined; provider returning 201 with an unexpected body shape; custom middleware transforming the response.","solutions":["Check the Bot Framework/teams adapter version for response-shape changes; pin a version where app.send returns the sent message resource.","Log the raw result of app.send at this call site to see the actual shape returned.","If using a mocked adapter in tests, make the mock return { id: '<messageId>' }.","Treat the send as unproven and re-send only after confirming no duplicate card was delivered (the runtime deliberately does not auto-retry)."],"exampleFix":"// before\nconst result = await teams.app.send(conversationId, { type: \"message\", attachments: [card] });\n// test double returning undefined\n// after\nconst result = (await teams.app.send(conversationId, { type: \"message\", attachments: [card] })) ?? { id: crypto.randomUUID() }; // only if your adapter guarantees delivery without an id\n// preferred: fix the adapter/mock to return the real message id","handlingStrategy":"type-guard","validationCode":"const result = await teams.app.send(conversationId, payload);\nconst hasReceipt = (r: unknown): r is { id: string } =>\n  typeof r === \"object\" && r !== null && typeof (r as any).id === \"string\" &&\n  (r as any).id.length > 0 && (r as any).id.length <= 1024 &&\n  !/[\\x00-\\x20\\x7f]/.test((r as any).id);\nif (!hasReceipt(result)) throw new Error(\"no usable message id in send receipt\");","typeGuard":"function hasMessageReceipt(r: unknown): r is { id: string } {\n  return typeof r === \"object\" && r !== null && typeof (r as { id?: unknown }).id === \"string\" &&\n    (r as { id: string }).id.length > 0 && (r as { id: string }).id.length <= 1024 &&\n    !/[\\x00-\\x20\\x7f]/.test((r as { id: string }).id);\n}","tryCatchPattern":"try {\n  const receipt = await teams.paperclipSendFileCard(threadId, kind, input);\n  recordDelivery(threadId, receipt.id);\n} catch (err) {\n  logger.error({ threadId }, \"send receipt unproven; treat delivery as unconfirmed\");\n  throw err;\n}","preventionTips":["Pin the Bot Framework/adapter version and test the send response shape on upgrades.","Make test doubles return realistic receipts ({ id: '...' }).","Log raw app.send results in staging to detect shape drift early."],"tags":["teams","response-validation","receipt"],"backgroundTag":"unexpected-response-shape","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"}