can1357/oh-my-pi · error · Error

"${params.action}" requires both "description" and "body".

Error message

"${params.action}" requires both "description" and "body".

What it means

The manage_skill schema's cross-field narrow already rejects create/update calls missing description or body, so this Error in execute() is a defensive narrowing guard: it proves the strings are present for writeManagedSkill's typed contract. It fires only when empty strings or falsy values slip past schema validation (e.g. description: "" passes the undefined check but fails this guard).

Source

Thrown at packages/coding-agent/src/tools/manage-skill.ts:69

		if (!session.settings.get("autolearn.enabled")) return null;
		return new ManageSkillTool(session.refreshSkills);
	}

	async execute(_id: string, params: ManageSkillParams): Promise<AgentToolResult> {
		if (params.action === "delete") {
			await deleteManagedSkill(params.name);
			await this.refreshSkills?.();
			return {
				content: [{ type: "text", text: `Deleted managed skill "${params.name}".` }],
				details: { action: "delete", name: params.name },
			};
		}

		// Defensive narrowing: the schema refine already rejects create/update
		// without both fields, so this is unreachable for valid input — it only
		// proves the strings are present to `writeManagedSkill`'s typed contract.
		if (!params.description || !params.body) {
			throw new Error(`"${params.action}" requires both "description" and "body".`);
		}
		// A managed skill resolves below any authored skill of the same name
		// (authored always wins in discovery), so creating one under a name an
		// authored skill already claims writes a file that never surfaces. Refuse
		// up front rather than report a false "Created". `sanitizeSkillName`
		// normalizes to the on-disk name the discovery scan compares against.
		if (params.action === "create" && isNameClaimedByAuthoredSkill(sanitizeSkillName(params.name))) {
			return {
				content: [
					{
						type: "text",
						text: `Cannot create managed skill "${params.name}": an authored skill of that name already exists, and managed skills cannot override authored ones. Choose a different name.`,
					},
				],
				isError: true,
				details: { action: "create", name: params.name, shadowed: true },
			};
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Include both a non-empty description and a non-empty body in the manage_skill params.
  2. If calling execute() directly, validate params against the schema first.
  3. Use action "delete" if you do not intend to provide skill content.

Example fix

// before: empty body slips past schema but fails the guard
{ "action": "create", "name": "deploy", "description": "How to deploy", "body": "" }
// after: provide real content
{ "action": "create", "name": "deploy", "description": "How to deploy", "body": "## Steps\n1. Run bun test" }
Defensive patterns

Strategy: validation

Validate before calling

if ((params.action === "create" || params.action === "update") &&
    (!params.description || !params.body)) {
  throw new Error("manage_skill create/update requires non-empty description and body.");
}

Type guard

function isCompleteSkillParams(p: ManageSkillParams): p is ManageSkillParams & { description: string; body: string } {
  return p.action === "delete" || (typeof p.description === "string" && p.description.length > 0 &&
    typeof p.body === "string" && p.body.length > 0);
}

Try / catch

try {
  await manageSkillTool.execute(id, params);
} catch (err) {
  if (err instanceof Error && err.message.includes('requires both "description" and "body"')) {
    // re-issue with filled-in description and body
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Invoking manage_skill (or calling execute directly, bypassing schema validation) with action "create" or "update" where description or body is an empty string or otherwise falsy.

Common situations: Programmatic/SDK calls to execute() that skip the schema narrow; a model emitting description: "" which passes the undefined-only check but fails the truthiness guard.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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