Yeachan-Heo/oh-my-codex · error · Error

Refusing malformed skill in ${entryPath}.

Error message

Refusing malformed skill in ${entryPath}.

What it means

Each active_skills entry must carry a non-empty string skill identifier; this error fires when entry.skill is missing, not a string, or an empty/whitespace-only string. The skill name is the key cancellation logic uses to match and update entries, so it must be present and well-formed. The library refuses rather than guessing a default.

Source

Thrown at src/cli/index.ts:8336


function assertCompatibleNestedSkillOwners(
  value: Record<string, unknown>,
  authority: ExactSessionCancellationAuthority,
  path: string,
): void {
  if (!Object.prototype.hasOwnProperty.call(value, "active_skills")) return;
  if (!Array.isArray(value.active_skills)) {
    throw new Error(`Refusing malformed active_skills in ${path}.`);
  }
  for (const [index, skill] of value.active_skills.entries()) {
    const entryPath = `${path} active_skills[${index}]`;
    if (!skill || typeof skill !== "object" || Array.isArray(skill)) {
      throw new Error(`Refusing malformed active_skills entry ${index} in ${path}.`);
    }
    const entry = skill as Record<string, unknown>;
    if (typeof entry.skill !== "string" || entry.skill.trim() === "") {
      throw new Error(`Refusing malformed skill in ${entryPath}.`);
    }
    for (const field of ["active"] as const) {
      if (Object.prototype.hasOwnProperty.call(entry, field) && typeof entry[field] !== "boolean") {
        throw new Error(`Refusing malformed ${field} in ${entryPath}.`);
      }
    }
    if (Object.prototype.hasOwnProperty.call(entry, "phase") && typeof entry.phase !== "string") {
      throw new Error(`Refusing malformed phase in ${entryPath}.`);
    }
    for (const [field, fieldValue] of Object.entries(entry)) {
      if (field.endsWith("_at") && typeof fieldValue !== "string") {
        throw new Error(`Refusing malformed ${field} in ${entryPath}.`);
      }
    }
    assertExactSkillOwner(entry, authority, entryPath);
  }
}

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Locate the entry at the reported active_skills index in the state file
  2. Set skill to a non-empty trimmed string or remove the entry
  3. Save and retry the cancel command
  4. Validate state files with a schema check after external tooling writes them

Example fix

// before
{ "active": true }
// after
{ "skill": "deploy", "active": true }
Defensive patterns

Strategy: validation

Validate before calling

function skillNamesValid(entries: Record<string, unknown>[]): boolean {
  return entries.every((e) => typeof e.skill === "string" && e.skill.trim() !== "");
}

Type guard

function isNamedSkillEntry(v: unknown): v is { skill: string } & Record<string, unknown> {
  return isSkillEntry(v) && typeof (v as Record<string, unknown>).skill === "string" && (v as { skill: string }).skill.trim() !== "";
}

Prevention

When it happens

Trigger: An entry like {}, {"skill": 42}, {"skill": ""}, or {"skill": " "} inside active_skills in the state file being cancelled against.

Common situations: Partial manual edits that added an entry without a skill name; schema drift between CLI versions; tools that emit empty placeholders for unused skill slots.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/ec8a6c7323f0073a. Report an issue: GitHub.