paperclipai/paperclip · error
Teams upload is not confirmed
Error message
Teams upload is not confirmed
What it means
fileInfo() builds the Teams 'file info' attachment card (contentType application/vnd.microsoft.teams.card.file.info) that is sent after the OneDrive upload completes. It refuses to run unless the capability's #confirmed flag is true, i.e. a previous exchange('put') classified the upload as uploaded. This prevents sending a download link for a file that was never actually committed to the recipient's OneDrive.
Source
Thrown at server/src/services/chat-teams-file-consent.ts:414
.parse(await openPrivate(context, "upload", material));
if (
value.bindingDigest !== digest(binding) ||
value.info.name !== binding.filename ||
typeof sharePointUrl(value.info.uploadUrl) === "string" ||
typeof sharePointUrl(value.info.contentUrl, true) === "string" ||
(value.confirmed && !value.putStarted)
)
throw new Error("Invalid Teams upload state");
const result = new UploadCapability(value.info, binding);
result.#confirmed = value.confirmed;
result.#putStarted = value.putStarted;
return result;
}
matches(binding: TeamsFileConsentBinding): boolean {
return this.#bindingDigest === digest(binding);
}
fileInfo() {
if (!this.#confirmed) throw new Error("Teams upload is not confirmed");
return {
contentType: "application/vnd.microsoft.teams.card.file.info" as const,
name: this.#info.name,
contentUrl: this.#info.contentUrl,
content: { uniqueId: this.#info.uniqueId, fileType: this.#info.fileType },
};
}
async exchange(
operation: "put" | "status",
bytes: Buffer | null,
options: UploadRequestOptions,
): Promise<TeamsUploadOutcome> {
if (
(operation !== "put" && operation !== "status") ||
options.byteSize !== this.#byteSize
)
throw new Error("Invalid Teams upload binding");
// The capability is itself a security boundary; calling it directly cannotView on GitHub (pinned to 01ad858492)
Solutions
- Only call fileInfo()/buildTeamsUploadedFileCard after exchange('put') returned an outcome with kind === 'uploaded'.
- After restoring from sealed state, check the confirmed flag first (via outcome of a status operation or by gating on the persisted phase reaching file_info_pending) before building the card.
- If the upload is incomplete, re-drive the state machine: exchange('put') or exchange('status') until kind === 'uploaded', advancing phases via nextTeamsFileConsentPhase.
- If the session expired (session_unavailable), restart the whole consent flow with a fresh binding rather than fabricating a file card.
Example fix
// before
const card = buildTeamsUploadedFileCard(upload, outcome); // throws if not uploaded
// after
if (outcome.kind !== "uploaded") {
throw new Error(`upload not complete: ${outcome.kind}${"reason" in outcome ? "/" + outcome.reason : ""}`);
}
const card = buildTeamsUploadedFileCard(upload, outcome); Defensive patterns
Strategy: type-guard
Validate before calling
const outcome = await exchangeTeamsFileUpload({ upload, binding, operation: "status" });
if (outcome.kind !== "uploaded") throw new Error("upload not confirmed; cannot send file card"); Type guard
function isConfirmedOutcome(outcome: TeamsUploadOutcome): outcome is Extract<TeamsUploadOutcome, { kind: "uploaded" }> {
return outcome.kind === "uploaded";
} Try / catch
try {
const card = buildTeamsUploadedFileCard(upload, outcome);
} catch (e) {
if (e instanceof Error && e.message === "Teams upload is not confirmed") {
// re-drive exchange("put")/exchange("status") until uploaded, or restart flow
return null;
}
throw e;
} Prevention
- Gate file-card sending on the persisted phase being file_info_pending/delivered via nextTeamsFileConsentPhase.
- Never call fileInfo() directly on a restored capability without checking confirmation state first.
- Handle uncertain/incomplete exchange outcomes explicitly; do not fall through to card building.
- On session_unavailable, restart the consent flow instead of sending a dead link.
When it happens
Trigger: Calling fileInfo() (directly or via buildTeamsUploadedFileCard) on an UploadCapability that was restored with confirmed=false, or on a freshly created capability whose exchange('put') has not yet returned { kind: 'uploaded' }, or whose PUT ended uncertain/incomplete.
Common situations: Worker resumes after restart and builds the file card before re-running the upload; caller ignores the exchange() outcome (uncertain/incomplete) and proceeds to send the card; the upload session expired and returned session_unavailable but downstream code treats the flow as done.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Invalid Teams upload binding
- Teams upload bytes do not match consent
- Completed warm transition template conflicts with its exact
- Warm transition result is not yet authenticated.
- Warm run transition target conflicts with its durable receip
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/bbd407318eb1f170.
Report an issue: GitHub.