can1357/oh-my-pi · error

Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_

Error message

Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_SKILL_BYTES}. Trim the body or description.

What it means

Managed SKILL.md files are capped at MAX_MANAGED_SKILL_BYTES (64,000) UTF-8 bytes, measured on the final file content (frontmatter + description + body). writeManagedSkill throws with the actual and maximum byte counts when the serialized content exceeds the cap.

Source

Thrown at packages/coding-agent/src/autolearn/managed-skills.ts:170

export async function writeManagedSkill(input: WriteManagedSkillInput): Promise<{ path: string }> {
	const name = sanitizeSkillName(input.name);
	const description = sanitizeManagedDescription(input.description);
	const body = input.body.trim();
	// Reject empty content: an all-whitespace/control description sanitizes to ""
	// and the `requireDescription` discovery scan then silently drops the skill,
	// so the tool would report success for a skill that never appears.
	if (!description) {
		throw new Error(`Managed skill "${name}" needs a non-empty description.`);
	}
	if (!body) {
		throw new Error(`Managed skill "${name}" needs a non-empty body.`);
	}
	const content = `${toSkillFrontmatter(name, description)}\n${body}\n`;
	// Cap the UTF-8 byte size of the FINAL file (body + description + frontmatter),
	// not the UTF-16 code-unit length of the body alone.
	const bytes = Buffer.byteLength(content, "utf8");
	if (bytes > MAX_MANAGED_SKILL_BYTES) {
		throw new Error(
			`Managed skill is ${bytes} bytes; the limit is ${MAX_MANAGED_SKILL_BYTES}. Trim the body or description.`,
		);
	}
	return serializeSkillMutation(name, async () => {
		await assertManagedRootSafe();
		const dir = path.join(getManagedSkillsDir(), name);
		const file = path.join(dir, "SKILL.md");
		// Reject a symlinked skill directory: an intermediate symlink would let the
		// write escape the isolated managed root. lstat does not follow the final
		// component, so a symlinked `dir` is caught here.
		const dirStat = await fs.lstat(dir).catch(err => {
			if (isEnoent(err)) return null;
			throw err;
		});
		if (dirStat?.isSymbolicLink()) {
			throw new Error(
				`Managed skill "${name}" resolves through a symlink; refusing to write outside the managed directory.`,
			);

View on GitHub (pinned to 9690622007)

Solutions

  1. Trim the body — keep only the essential instructions/steps
  2. Shorten the description to one concise line
  3. Measure with Buffer.byteLength(content, 'utf8') before writing to confirm you are under 64,000 bytes
  4. If the content is genuinely large, summarize it or store the full content elsewhere and reference it

Example fix

// before
writeManagedSkill({ name: "foo", description: desc, body: hugeLogDump });
// after
const body = hugeLogDump.split("\n").slice(0, 200).join("\n"); // keep essentials
writeManagedSkill({ name: "foo", description: desc, body });
Defensive patterns

Strategy: validation

Validate before calling

const content = `${name}\n${description}\n${body}`;
if (Buffer.byteLength(content, "utf8") > 64_000) throw new Error("skill too large");

Try / catch

try {
  await writeManagedSkill(input);
} catch (err) {
  const m = /Managed skill is (\d+) bytes/.exec(String((err as Error).message));
  if (m) {
    // trim body/description and retry
  } else throw err;
}

Prevention

When it happens

Trigger: writeManagedSkill called with a body (or description) whose combined UTF-8 size exceeds 64,000 bytes — note multi-byte characters count more than their string length suggests.

Common situations: Auto-learn dumps a large log or entire file into the body; content with many non-ASCII characters inflates byte size past the limit even when string length looks fine; accumulated 'enhancement' appends grow the file over sessions.

Related errors


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