can1357/oh-my-pi · error · ToolError

fill is mutually exclusive with title and body

Error message

fill is mutually exclusive with title and body

What it means

`fill` is an alternative to explicit title/body, not a combination: fill derives title and body from commit messages, and supplying any of title or body alongside it is ambiguous. The tool rejects the call up front rather than letting gh fail or silently preferring one source.

Source

Thrown at packages/coding-agent/src/tools/gh-pr-checkout.ts:591

	params: GithubInput,
	signal: AbortSignal | undefined,
): Promise<AgentToolResult<GhToolDetails>> {
	const repo = normalizeOptionalString(params.repo);
	const title = normalizeOptionalString(params.title);
	const body = params.body;
	const base = normalizeOptionalString(params.base);
	const head = normalizeOptionalString(params.head);
	const draft = params.draft ?? false;
	const fill = params.fill ?? false;
	const reviewers = normalizePrIdentifierList(params.reviewer);
	const assignees = normalizePrIdentifierList(params.assignee);
	const labels = normalizePrIdentifierList(params.label);

	if (!fill && !title) {
		throw new ToolError("title is required unless fill is true");
	}
	if (fill && (title || body !== undefined)) {
		throw new ToolError("fill is mutually exclusive with title and body");
	}

	const args = ["pr", "create"];
	appendRepoFlag(args, repo);
	if (title) args.push("--title", title);
	if (base) args.push("--base", base);
	if (head) args.push("--head", head);
	if (draft) args.push("--draft");
	if (fill) args.push("--fill");
	for (const reviewer of reviewers) args.push("--reviewer", reviewer);
	for (const assignee of assignees) args.push("--assignee", assignee);
	for (const label of labels) args.push("--label", label);

	let bodyDir: string | undefined;
	try {
		if (!fill) {
			if (body !== undefined && body.length > 0) {
				// Route through a temp file so multi-KB bodies stay clear of any

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the title/body parameters and rely solely on fill=true to generate them from commits.
  2. Or remove fill (leave it false) and supply your explicit title/body.
  3. If a framework injects a default empty body, set it to undefined (not "") so fill can be used.

Example fix

// before
op pr_create --fill --title "Fix login redirect"
// after (choose one)
op pr_create --fill
op pr_create --title "Fix login redirect" --body "Fixes #42"
Defensive patterns

Strategy: validation

Validate before calling

if (params.fill && (params.title || params.body !== undefined)) {
  throw new Error("remove title/body when using fill=true");
}

Type guard

function fillExclusive(p: { fill?: boolean; title?: string; body?: string }): boolean {
  return !(p.fill && (p.title !== undefined || p.body !== undefined));
}

Try / catch

try {
  await op.prCreate(params);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("mutually exclusive")) {
    const { fill, title, body, ...rest } = params;
    await op.prCreate(fill ? rest : { ...rest, title, body }); // pick one mode
  } else throw err;
}

Prevention

When it happens

Trigger: Calling pr_create with fill=true together with a non-empty title, or fill=true together with a body parameter (body present, even empty string, counts as `body !== undefined`).

Common situations: Template/default params that always include a body field, merged with a fill flag from a different code path; users thinking fill only affects the body while supplying a custom title.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/d137ba6750de2693. Report an issue: GitHub.