{"record":{"id":"4c73d8414e94e61c","repo":"different-ai/openwork","slug":"name-is-required","errorCode":null,"errorMessage":"name is required","messagePattern":"name is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.mjs","lineNumber":1536,"sourceCode":"    .sort();\n}\n\nasync function writeCommandFile(scope, projectDir, command) {\n  const safeName = sanitizeCommandName(command?.name);\n  if (!safeName) {\n    throw new Error(\"command.name is required\");\n  }\n  const commandsDir = resolveCommandsDir(scope, projectDir);\n  await mkdir(commandsDir, { recursive: true });\n  const filePath = path.join(commandsDir, `${safeName}.md`);\n  await writeFile(filePath, serializeCommandFrontmatter({ ...command, name: safeName }), \"utf8\");\n  return execResult(true, `Wrote ${filePath}`);\n}\n\nasync function deleteCommandFile(scope, projectDir, name) {\n  const safeName = sanitizeCommandName(name);\n  if (!safeName) {\n    throw new Error(\"name is required\");\n  }\n  const commandsDir = resolveCommandsDir(scope, projectDir);\n  const filePath = path.join(commandsDir, `${safeName}.md`);\n  if (await pathExists(filePath)) {\n    await rm(filePath, { force: true });\n  }\n  return execResult(true, `Deleted ${filePath}`);\n}\n\nasync function collectProjectSkillRoots(projectDir) {\n  const roots = [];\n  let current = path.resolve(projectDir);\n\n  while (true) {\n    const opencodeSkills = path.join(current, \".opencode\", \"skills\");\n    const legacySkills = path.join(current, \".opencode\", \"skill\");\n    const claudeSkills = path.join(current, \".claude\", \"skills\");\n","sourceCodeStart":1518,"sourceCodeEnd":1554,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/desktop/electron/main.mjs#L1518-L1554","documentation":"deleteCommandFile sanitizes the name argument before constructing the <name>.md path inside the commands directory. If name is missing/empty or sanitizes to nothing, it throws 'name is required' to avoid deleting an undefined or dangerous path.","triggerScenarios":"Calling the command-delete API with name=undefined/null/empty string, or a name made only of characters stripped by sanitizeCommandName (slashes, whitespace).","commonSituations":"Deleting from a stale list where the command row lost its name, passing the command's display label (stripped to nothing) instead of its filename-safe name, or wiring the delete handler without binding the name argument.","solutions":["Pass the exact command name as it appears in the commands directory listing.","Verify the delete handler actually forwards the name (not an event object or index).","Trim and sanity-check the name is non-empty before invoking.","Re-list commands (listCommandNames) and delete using a fresh, valid name."],"exampleFix":"// before\nawait deleteCommandFile(\"workspace\", dir, undefined);\n// after\nawait deleteCommandFile(\"workspace\", dir, \"run-tests\");","handlingStrategy":"validation","validationCode":"const safe = String(name ?? '').trim();\nif (!safe || /[\\\\/]/.test(safe)) {\n  throw new Error('name must be a non-empty filename-safe string');\n}","typeGuard":"function isDeletableCommandName(name) {\n  return typeof name === 'string' && name.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Bind the command name explicitly in delete handlers; never forward raw event objects.","Re-list commands before delete to confirm the exact name exists.","Use the filename-safe name, not a display label, when deleting."],"tags":["validation","arguments","commands"],"backgroundTag":"missing-required-argument","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}