paperclipai/paperclip · error
Teams private state could not be restored
Error message
Teams private state could not be restored
What it means
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.
Source
Thrown at server/src/services/chat-teams-file-consent.ts:152
);
if (Buffer.byteLength(plaintext) > 64 * 1024) throw new Error();
const envelope = z
.object({
schema: z.literal("paperclip.teams.file-private.v1"),
context: privateContextSchema,
purpose: z.string(),
value: z.unknown(),
})
.strict()
.parse(JSON.parse(plaintext));
if (
envelope.purpose !== purpose ||
digest(envelope.context) !== digest(parsedContext)
)
throw new Error();
return envelope.value;
} catch {
throw new Error("Teams private state could not be restored");
}
}
export async function sealTeamsFileConsentBinding(
context: TeamsFilePrivateContext,
binding: TeamsFileConsentBinding,
) {
const parsed = parseTeamsFileConsentBinding(binding);
if (
!parsed ||
parsed.companyId !== context.companyId ||
parsed.endpointId !== context.endpointId
)
throw new Error("Invalid Teams private binding");
return sealPrivate(context, "binding", parsed);
}
export async function restoreTeamsFileConsentBinding(View on GitHub (pinned to 01ad858492)
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.
Example fix
// before const binding = await restoreTeamsFileConsentBinding(currentTransferContext, materialFromOtherRow); // after const binding = await restoreTeamsFileConsentBinding(originalSealContext, materialFromSameRow);
Defensive patterns
Strategy: try-catch
Validate before calling
import { z } from 'zod';
const 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);
const sized = material && JSON.stringify(material).length <= 128 * 1024;
if (!ctx.success || !sized) throw new Error('context or material invalid before restore'); Type guard
function isSealableContext(c: unknown): c is TeamsFilePrivateContext {
return typeof c === 'object' && c !== null &&
'companyId' in c && 'endpointId' in c && 'transferId' in c &&
'authorityDigest' in c;
} Try / catch
try {
const binding = await restoreTeamsFileConsentBinding(context, material);
} catch (err) {
if (err instanceof Error && err.message === 'Teams private state could not be restored') {
// start a fresh consent flow; do not retry restore blindly
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Invalid Teams upload state
- Teams destination is missing its verified service URL
- Teams destination contains an invalid service URL
- Teams destination contains an untrusted service URL
- decodeThreadId is unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/1c2ebc340cc2d23b.
Report an issue: GitHub.