{"record":{"id":"1c2ebc340cc2d23b","repo":"paperclipai/paperclip","slug":"teams-private-state-could-not-be-restored","errorCode":null,"errorMessage":"Teams private state could not be restored","messagePattern":"Teams private state could not be restored","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-teams-file-consent.ts","lineNumber":152,"sourceCode":"    );\n    if (Buffer.byteLength(plaintext) > 64 * 1024) throw new Error();\n    const envelope = z\n      .object({\n        schema: z.literal(\"paperclip.teams.file-private.v1\"),\n        context: privateContextSchema,\n        purpose: z.string(),\n        value: z.unknown(),\n      })\n      .strict()\n      .parse(JSON.parse(plaintext));\n    if (\n      envelope.purpose !== purpose ||\n      digest(envelope.context) !== digest(parsedContext)\n    )\n      throw new Error();\n    return envelope.value;\n  } catch {\n    throw new Error(\"Teams private state could not be restored\");\n  }\n}\n\nexport async function sealTeamsFileConsentBinding(\n  context: TeamsFilePrivateContext,\n  binding: TeamsFileConsentBinding,\n) {\n  const parsed = parseTeamsFileConsentBinding(binding);\n  if (\n    !parsed ||\n    parsed.companyId !== context.companyId ||\n    parsed.endpointId !== context.endpointId\n  )\n    throw new Error(\"Invalid Teams private binding\");\n  return sealPrivate(context, \"binding\", parsed);\n}\n\nexport async function restoreTeamsFileConsentBinding(","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-teams-file-consent.ts#L134-L170","documentation":"openPrivate decrypts and re-validates sealed Teams file-consent private state (binding, upload, response envelopes) using the local_encrypted secret provider. It throws this generic message for ANY failure inside the restore path: malformed ciphertext, oversized material, wrong schema/purpose, context digest mismatch, or provider decryption failure. The original cause is deliberately swallowed so no plaintext or provider detail leaks.","triggerScenarios":"Calling restoreTeamsFileConsentBinding / UploadCapability.restore / ConsentEvent.restore with (a) ciphertext that fails the secret provider's resolveVersion, (b) material JSON >128KB or plaintext >64KB, (c) an envelope whose purpose does not match ('binding'/'upload'/'response'), or (d) an envelope.context digest differing from the current TeamsFilePrivateContext (companyId/endpointId/transferId/authorityDigest changed).","commonSituations":"Restoring state after a server restart against a database row whose context changed (endpoint re-provisioned, new transferId, rotated authorityDigest); pointing a restore call at the wrong row's ciphertext; mixing 'binding' material into an 'upload' restore; tampered or hand-edited DB rows; secret-provider key rotation making old ciphertext undecryptable.","solutions":["Verify the TeamsFilePrivateContext passed in exactly matches the one used at seal time: companyId, endpointId, transferId and authorityDigest must all be identical (digest comparison is strict).","Confirm you are passing the correct TeamsFileCiphertext loaded from the exact locked transfer row, and the purpose matches the API (binding material to restoreTeamsFileConsentBinding, upload material to restoreTeamsFileUpload).","Check the local_encrypted secret provider is configured with the same key that sealed the state; re-seal if keys were rotated.","If state cannot be restored legitimately, treat it as unrecoverable per the module's contract: restart the flow with a fresh consent card and a new sealed binding rather than retrying restore."],"exampleFix":"// before\nconst binding = await restoreTeamsFileConsentBinding(currentTransferContext, materialFromOtherRow);\n// after\nconst binding = await restoreTeamsFileConsentBinding(originalSealContext, materialFromSameRow);","handlingStrategy":"try-catch","validationCode":"import { z } from 'zod';\nconst ctx = z.object({ companyId: z.string().uuid(), endpointId: z.string().uuid(), transferId: z.string().uuid(), authorityDigest: z.string().regex(/^[a-f0-9]{64}$/) }).safeParse(context);\nconst sized = material && JSON.stringify(material).length <= 128 * 1024;\nif (!ctx.success || !sized) throw new Error('context or material invalid before restore');","typeGuard":"function isSealableContext(c: unknown): c is TeamsFilePrivateContext {\n  return typeof c === 'object' && c !== null &&\n    'companyId' in c && 'endpointId' in c && 'transferId' in c &&\n    'authorityDigest' in c;\n}","tryCatchPattern":"try {\n  const binding = await restoreTeamsFileConsentBinding(context, material);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Teams private state could not be restored') {\n    // start a fresh consent flow; do not retry restore blindly\n  }\n}","preventionTips":["Persist the exact TeamsFilePrivateContext alongside the ciphertext and reload it from the same row at restore time.","Never reuse contexts across transfers; key sealed state by transferId.","Treat secret-provider key rotation as invalidating old sealed state; plan re-consent flows."],"tags":["encryption","state-restore","teams","context-mismatch"],"backgroundTag":"checksum-mismatch","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"}