paperclipai/paperclip · error
requires
Error message
${verb} requires ${verb === "reply" ? "an existing conversation" : "a parent task and a new conversation"} What it means
The Paperclip CLI email command enforces an invariant between the subcommand verb and the payload: 'reply' must carry a conversationId pointing at an existing conversation, while 'send' must NOT carry one (it creates a new conversation under a parent task). The command derives this from `(verb === "reply") !== Boolean(input.conversationId)` after parsing the JSON input file, and throws this Error when the two disagree.
Solutions
- For `reply`: add the `conversationId` of the existing conversation to the JSON input file.
- For `send`: remove the `conversationId` field from the JSON input file so a new conversation is created under the parent task.
- Check which verb you actually intend and pass the matching `--file` payload; keep separate templates for send and reply.
Example fix
// before (email send fails)
{ "taskId": "t_123", "conversationId": "c_456", "body": "hi" }
// after
{ "taskId": "t_123", "body": "hi" } Defensive patterns
Strategy: validation
Validate before calling
function validateEmailPayload(verb, input) {
if ((verb === "reply") !== Boolean(input.conversationId)) {
throw new Error(verb === "reply"
? "reply requires conversationId of an existing conversation"
: "send must not include conversationId (a new conversation is created)");
}
} Type guard
const isReplyPayload = (input) => typeof input.conversationId === "string" && input.conversationId.length > 0;
Try / catch
try {
await api.post(`/api/companies/${companyId}/email/send`, input);
} catch (err) {
if (err.message.includes("requires")) {
console.error("Verb/payload mismatch: reply needs conversationId; send must omit it.");
} else throw err;
} Prevention
- Keep separate JSON templates for send and reply; never reuse one for both.
- Strip conversationId before building a send payload.
- For replies, resolve the conversationId from the thread you are answering before writing the file.
- Validate payloads with the emailSendSchema locally before invoking the CLI.
When it happens
Trigger: Running `paperclip email reply --file msg.json` where msg.json omits `conversationId`, or running `paperclip email send --file msg.json` where msg.json unexpectedly includes a `conversationId`. The parsed payload fails the verb/payload consistency check in cli/src/commands/client/email.ts:41.
Common situations: Reusing a reply JSON file for a fresh send (stale conversationId left in the file); hand-writing a reply payload and forgetting the conversationId; copy-pasting a send example and adding conversationId thinking it is required; scripting bulk email where a template mixes both cases.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- A safe, unique --revision is required
- --api-key-env must name a valid environment variable.
- --api-key-secret-id must be a UUID
- Expected a JSON object
- --limit must be a positive integer
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/3a264895c7a85299.
Report an issue: GitHub.
Appendix: source
Thrown at cli/src/commands/client/email.ts:41
{ json: true },
);
});
for (const verb of ["send", "reply"] as const) {
addCommonClientOptions(
email
.command(verb)
.requiredOption(
"--file <path>",
"JSON request file, including a stable idempotencyKey",
),
{ includeCompany: true },
).action(async (opts: BaseClientOptions & { file: string }) => {
const ctx = resolveCommandContext(opts, { requireCompany: true });
const input = emailSendSchema.parse(
JSON.parse(await readFile(opts.file, "utf8")),
);
if ((verb === "reply") !== Boolean(input.conversationId))
throw new Error(
`${verb} requires ${verb === "reply" ? "an existing conversation" : "a parent task and a new conversation"}`,
);
printOutput(
await ctx.api.post(`/api/companies/${ctx.companyId}/email/send`, input),
{ json: true },
);
});
}
addCommonClientOptions(
email.command("thread").argument("<issueId>", "Email task ID"),
{ includeCompany: true },
).action(async (issueId: string, opts: BaseClientOptions) => {
const ctx = resolveCommandContext(opts, { requireCompany: true });
printOutput(
await ctx.api.get(
`/api/companies/${ctx.companyId}/email/tasks/${encodeURIComponent(issueId)}`,
),
{ json: true },View on GitHub (pinned to 3f1d897a7c)