{"record":{"id":"688c8d0daac720f9","repo":"chatboxai/chatbox","slug":"invalid-file-entry-for-name","errorCode":null,"errorMessage":"invalid file entry for \"${name}\"","messagePattern":"invalid file entry for \"(.+?)\"","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/skills/builtin-sync.ts","lineNumber":176,"sourceCode":"  const normalized = path.normalize(relativePath)\n  // 顶层保留文件大小写不敏感比较：case-insensitive 文件系统（macOS/Windows）上\n  // \"skill.md\" 与生成的 \"SKILL.md\" 指向同一文件，若放行会覆盖 frontmatter+body。\n  const lower = normalized.toLowerCase()\n  if (normalized === '.' || lower === 'skill.md' || lower === 'source.json') return null\n  if (normalized.startsWith('..') || path.isAbsolute(normalized)) return null\n\n  const target = path.resolve(skillDir, normalized)\n  const root = path.resolve(skillDir)\n  if (target !== root && !target.startsWith(root + path.sep)) return null\n  return { target, normalizedPath: normalized.split(path.sep).join('/') }\n}\n\nfunction prepareSnapshotFiles(name: string, skillDir: string, files: RemoteSkillFile[]): SnapshotFileTarget[] {\n  const targets: SnapshotFileTarget[] = []\n  const seenPaths = new Set<string>()\n  for (const file of files) {\n    if (!file || typeof file.path !== 'string' || typeof file.content !== 'string') {\n      throw new Error(`invalid file entry for \"${name}\"`)\n    }\n    const resolved = resolveSnapshotFilePath(skillDir, file.path)\n    if (!resolved) {\n      throw new Error(`invalid file path for \"${name}\": ${file.path}`)\n    }\n    if (seenPaths.has(resolved.normalizedPath)) {\n      throw new Error(`duplicate file path for \"${name}\": ${resolved.normalizedPath}`)\n    }\n    seenPaths.add(resolved.normalizedPath)\n    targets.push({ file, target: resolved.target, normalizedPath: resolved.normalizedPath })\n  }\n  return targets\n}\n\n/** 将 skill 内容写入快照目录的 SKILL.md（frontmatter + body）和附属文件，与 parser 的解析格式一致。 */\nfunction writeSnapshotSkill(\n  name: string,\n  metadata: SkillMetadata,","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/builtin-sync.ts#L158-L194","documentation":"Thrown by prepareSnapshotFiles when iterating a remote skill's files list and encountering an entry that is not a non-null object with string path AND string content. It is the schema validation gate before any filesystem path resolution; a single malformed entry aborts the whole snapshot write.","triggerScenarios":"RemoteSkillFile payload from the server contains an entry where path or content is missing/number/null, or the entry itself is null/undefined. Triggered during skill snapshot sync (builtin-sync) when materializing remote skill files on disk.","commonSituations":"Backend schema regression returning {path, contentBase64} instead of {path, content}; partial/truncated fetch; a server-side null for an optional field mistakenly placed inside the files array; client/server version mismatch on the RemoteSkillFile shape.","solutions":["Verify the server returns files as {path: string, content: string} per RemoteSkillFile; align client and server versions.","If a malformed entry should be skippable, filter before prepareSnapshotFiles rather than letting it abort the sync.","Add a runtime schema check (zod/ajv) on the fetched payload to fail with a precise message naming the bad field.","Log the offending entry (without content) to identify which remote skill is malformed."],"exampleFix":"// before\nif (!file || typeof file.path !== 'string' || typeof file.content !== 'string') throw new Error(`invalid file entry for \"${name}\"`)\n\n// after: skip bad entries and warn\nif (!file || typeof file.path !== 'string' || typeof file.content !== 'string') { log.warn(`invalid file entry for \"${name}\": ${JSON.stringify({ ...file, content: undefined })}`); continue }","handlingStrategy":"type-guard","validationCode":"const valid = files.every((f): f is RemoteSkillFile => !!f && typeof f.path === 'string' && typeof f.content === 'string')\nif (!valid) throw new Error(`invalid file entry for \"${name}\"`)","typeGuard":"function isRemoteSkillFile(f: unknown): f is RemoteSkillFile {\n  return !!f && typeof (f as any).path === 'string' && typeof (f as any).content === 'string'\n}","tryCatchPattern":"try { await syncSkill(name, files) } catch (e) { if (/invalid file entry/.test((e as Error).message)) { log.warn(`remote skill '${name}' has malformed files; skipping`); return } throw e }","preventionTips":["Validate the RemoteSkillFile shape (zod/ajv) at the fetch boundary.","Keep client and server versions in sync for the payload schema.","Filter malformed entries instead of aborting the whole skill if partial sync is acceptable."],"tags":["skills","builtin-sync","validation","schema","typescript"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}