paperclipai/paperclip · error · UnsafeChatPublicationError
Chat publication source must be explicitly classified for…
Error message
Chat publication source must be explicitly classified for external delivery
What it means
projectSafeChatPublication is the only API allowed to build provider-bound external chat payloads, and it requires the caller to explicitly opt in: input.classification must equal "external" AND input.source must be one of PUBLICATION_SOURCES ("agent_comment", "explicit_board_send", "safe_milestone", "issue_interaction", "task_control"). If either check fails, it throws, preventing internal-only messages (agent chatter, logs) from leaking to external chat providers via a missing or default classification.
Solutions
- Set classification: "external" explicitly on every projection input intended for external delivery
- Use one of the five valid sources: "agent_comment", "explicit_board_send", "safe_milestone", "issue_interaction", "task_control"
- If you added a new legitimate source type, register it in PUBLICATION_SOURCES in server/src/services/chat-publication-projection.ts
- Check retry/enqueue code paths (e.g. enqueueFailedChatRetryPublications) to ensure they preserve the original classification and source fields
Example fix
// before
projectSafeChatPublication({ text, source: internalSource });
// after
projectSafeChatPublication({ text, classification: "external", source: "agent_comment" }); Defensive patterns
Strategy: try-catch
Validate before calling
const PUBLICATION_SOURCES = new Set(["agent_comment","explicit_board_send","safe_milestone","issue_interaction","task_control"]);
if (input.classification !== "external" || !PUBLICATION_SOURCES.has(input.source)) {
throw new Error(`Publication not classified for external delivery: ${input.classification}/${input.source}`);
} Type guard
type ExternalSource = "agent_comment" | "explicit_board_send" | "safe_milestone" | "issue_interaction" | "task_control";
function isExternalProjectionInput(i: { classification?: string; source: string }): i is { classification: "external"; source: ExternalSource } {
return i.classification === "external" && PUBLICATION_SOURCES.has(i.source as ExternalSource);
} Try / catch
try {
const payload = projectSafeChatPublication(input);
} catch (err) {
if (err instanceof UnsafeChatPublicationError && err.message.includes("explicitly classified for external delivery")) {
logger.warn("dropping publication missing external classification", { source: input.source });
return null; // never guess a classification for external delivery
}
throw err;
} Prevention
- Make classification a required field in the type constructing projection inputs so it cannot be omitted
- Centralize publication creation in one helper that always stamps a valid classification/source
- When adding a new source kind, update PUBLICATION_SOURCES in the same PR and add a test
- Audit retry paths to confirm they copy classification and source, not rebuild the input from partial data
When it happens
Trigger: Calling projectSafeChatPublication with classification left undefined/default (anything other than "external"), or with a source string outside the allowed set — e.g. "internal_comment", "debug", "agent_log", a typo like "agent_Comment", or a newly added source not registered in PUBLICATION_SOURCES.
Common situations: A new publication source was added elsewhere in the codebase but PUBLICATION_SOURCES in chat-publication-projection.ts was not updated; a caller forgot to set classification after a refactor; a retry/enqueue path reconstructs the input and drops the classification field.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- ACPX profile requires exact model ; received
- ACPX model must not be empty
- ACPX provider identity contains an invalid permission mode
- ACPX provider identity contains invalid lifetime fences
- AWS AgentCore evals require exact model…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/7cbd18d3c34053ec.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/chat-publication-projection.ts:386
title,
...(body ? { body } : {}),
...(actions.length ? { actions } : {}),
},
};
}
/**
* Builds the complete provider-bound payload. This is intentionally the only
* API that accepts attachments or rich interaction metadata.
*/
export function projectSafeChatPublication(
input: ChatPublicationProjectionInput,
): ProjectedSafeChatPublicationPayload {
if (
input.classification !== "external" ||
!PUBLICATION_SOURCES.has(input.source)
) {
throw new UnsafeChatPublicationError(
"Chat publication source must be explicitly classified for external delivery",
);
}
if (input.progressState && !PROGRESS_STATES.has(input.progressState)) {
throw new UnsafeChatPublicationError(
"External chat progress state is invalid",
);
}
const attachmentIds = projectAttachmentIds(input.attachmentIds);
const interaction = input.interaction ? projectCard(input.interaction) : null;
return {
text: projectSafeChatPublicationText(input.text),
...(attachmentIds ? { attachmentIds } : {}),
...(input.progressState ? { progressState: input.progressState } : {}),
...(interaction ?? {}),
};
}View on GitHub (pinned to 3f1d897a7c)