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

  1. For `reply`: add the `conversationId` of the existing conversation to the JSON input file.
  2. For `send`: remove the `conversationId` field from the JSON input file so a new conversation is created under the parent task.
  3. 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

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


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)