{"record":{"id":"b655b7d13df258ac","repo":"chatboxai/chatbox","slug":"skill-name-and-script-name-are-required","errorCode":null,"errorMessage":"Skill name and script name are required","messagePattern":"Skill name and script name are required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/skills/ipc-handlers.ts","lineNumber":174,"sourceCode":"      await shell.openPath(skillsDir)\n      return { success: true }\n    } catch (error) {\n      log.error('skills:open-directory failed', error)\n      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }\n    }\n  })\n\n  ipcMain.handle(\n    'skills:execute-script',\n    async (\n      _event,\n      params: { skillName: string; scriptName: string; args?: string[] }\n    ): Promise<{ success: boolean; stdout: string; stderr: string; exitCode: number | null }> => {\n      const { skillName, scriptName, args = [] } = params\n\n      try {\n        if (!skillName || !scriptName) {\n          throw new Error('Skill name and script name are required')\n        }\n\n        if (skillName.includes('..') || skillName.includes('/') || skillName.includes('\\\\')) {\n          throw new Error('Invalid skill name: path traversal not allowed')\n        }\n\n        if (scriptName.includes('..') || scriptName.includes('/') || scriptName.includes('\\\\')) {\n          throw new Error('Invalid script name: path traversal not allowed')\n        }\n\n        const skillsDir = getSkillsDir()\n        const scriptPath = path.join(skillsDir, skillName, 'scripts', scriptName)\n        if (!fs.existsSync(scriptPath)) {\n          throw new Error(`Script not found: ${scriptName}`)\n        }\n        const resolvedSkillsDir = fs.realpathSync(skillsDir)\n        const resolvedScriptPath = fs.realpathSync(scriptPath)\n        if (!resolvedScriptPath.startsWith(`${resolvedSkillsDir}${path.sep}`)) {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/ipc-handlers.ts#L156-L192","documentation":"Thrown by the skills:execute-script IPC handler when skillName or scriptName is falsy (empty string, undefined, null). This is the first guard before any filesystem access, validating that the renderer sent both required identifiers. The handler signature types them as string, but IPC payloads cross the trust boundary and are not guaranteed to match the declared type.","triggerScenarios":"The renderer invokes ipcRenderer.invoke('skills:execute-script', { skillName: '', scriptName: 'foo.sh' }) or omits scriptName entirely. Happens with malformed IPC calls, a stale renderer bundle whose payload shape changed, or a bug in the calling code that constructs the params object.","commonSituations":"A skill card UI sends an empty skillName because the selected skill was uninstalled between render and click; a plugin/script-runner integration passes undefined because it reads from an optional field; version skew between main and renderer after a partial update.","solutions":["Inspect the IPC call site in the renderer to confirm both skillName and scriptName are non-empty strings before invoking.","Add a defensive guard in the renderer that disables the run button when either field is empty.","If using a custom integration, validate the params object shape against the handler's expected type before send."],"exampleFix":"// before\nif (!skillName || !scriptName) {\n  throw new Error('Skill name and script name are required')\n}\n\n// renderer-side guard\nif (!skillName?.trim() || !scriptName?.trim()) {\n  showToast('Skill name and script name are required')\n  return\n}\nawait window.electron.ipcRenderer.invoke('skills:execute-script', { skillName, scriptName, args })","handlingStrategy":"validation","validationCode":"function validateExecuteScriptParams(params: unknown): params is { skillName: string; scriptName: string; args?: string[] } {\n  if (!params || typeof params !== 'object') return false\n  const p = params as any\n  return typeof p.skillName === 'string' && p.skillName.trim().length > 0\n    && typeof p.scriptName === 'string' && p.scriptName.trim().length > 0\n    && (p.args === undefined || Array.isArray(p.args))\n}\nif (!validateExecuteScriptParams(params)) throw new Error('Invalid params')","typeGuard":"function hasRequiredFields(p: unknown): p is { skillName: string; scriptName: string } {\n  return typeof (p as any)?.skillName === 'string' && !!((p as any).skillName).trim()\n    && typeof (p as any)?.scriptName === 'string' && !!((p as any).scriptName).trim()\n}","tryCatchPattern":"ipcRenderer.invoke('skills:execute-script', params).then((res) => {\n  if (!res.success) showError(res.stderr)\n})\n// handler already catches internally and returns { success: false, stderr: message }","preventionTips":["Disable the run-script UI button when skillName or scriptName is empty.","Validate the IPC payload shape in the renderer before invoking.","Treat IPC params as untyped at the boundary — never trust the declared TS type."],"tags":["ipc","validation","skills","electron","input-validation"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}