{"record":{"id":"8f1e6d7d8d79fa4f","repo":"can1357/oh-my-pi","slug":"params-action-requires-both-description-and","errorCode":null,"errorMessage":"\"${params.action}\" requires both \"description\" and \"body\".","messagePattern":"\"(.+?)\" requires both \"description\" and \"body\"\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/manage-skill.ts","lineNumber":69,"sourceCode":"\t\tif (!session.settings.get(\"autolearn.enabled\")) return null;\n\t\treturn new ManageSkillTool(session.refreshSkills);\n\t}\n\n\tasync execute(_id: string, params: ManageSkillParams): Promise<AgentToolResult> {\n\t\tif (params.action === \"delete\") {\n\t\t\tawait deleteManagedSkill(params.name);\n\t\t\tawait this.refreshSkills?.();\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Deleted managed skill \"${params.name}\".` }],\n\t\t\t\tdetails: { action: \"delete\", name: params.name },\n\t\t\t};\n\t\t}\n\n\t\t// Defensive narrowing: the schema refine already rejects create/update\n\t\t// without both fields, so this is unreachable for valid input — it only\n\t\t// proves the strings are present to `writeManagedSkill`'s typed contract.\n\t\tif (!params.description || !params.body) {\n\t\t\tthrow new Error(`\"${params.action}\" requires both \"description\" and \"body\".`);\n\t\t}\n\t\t// A managed skill resolves below any authored skill of the same name\n\t\t// (authored always wins in discovery), so creating one under a name an\n\t\t// authored skill already claims writes a file that never surfaces. Refuse\n\t\t// up front rather than report a false \"Created\". `sanitizeSkillName`\n\t\t// normalizes to the on-disk name the discovery scan compares against.\n\t\tif (params.action === \"create\" && isNameClaimedByAuthoredSkill(sanitizeSkillName(params.name))) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `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.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tisError: true,\n\t\t\t\tdetails: { action: \"create\", name: params.name, shadowed: true },\n\t\t\t};\n\t\t}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/manage-skill.ts#L51-L87","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Include both a non-empty description and a non-empty body in the manage_skill params.","If calling execute() directly, validate params against the schema first.","Use action \"delete\" if you do not intend to provide skill content."],"exampleFix":"// before: empty body slips past schema but fails the guard\n{ \"action\": \"create\", \"name\": \"deploy\", \"description\": \"How to deploy\", \"body\": \"\" }\n// after: provide real content\n{ \"action\": \"create\", \"name\": \"deploy\", \"description\": \"How to deploy\", \"body\": \"## Steps\\n1. Run bun test\" }","handlingStrategy":"validation","validationCode":"if ((params.action === \"create\" || params.action === \"update\") &&\n    (!params.description || !params.body)) {\n  throw new Error(\"manage_skill create/update requires non-empty description and body.\");\n}","typeGuard":"function isCompleteSkillParams(p: ManageSkillParams): p is ManageSkillParams & { description: string; body: string } {\n  return p.action === \"delete\" || (typeof p.description === \"string\" && p.description.length > 0 &&\n    typeof p.body === \"string\" && p.body.length > 0);\n}","tryCatchPattern":"try {\n  await manageSkillTool.execute(id, params);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('requires both \"description\" and \"body\"')) {\n    // re-issue with filled-in description and body\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always validate params against the schema (which enforces the cross-field narrow) before direct execute() calls.","Never pass empty strings for description/body — the guard requires truthy values, not just defined ones.","Use action \"delete\" when no content is intended."],"tags":["validation","schema","defensive-check","tool-params"],"backgroundTag":"missing-required-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}