{"record":{"id":"e18dbfedc784823b","repo":"chatboxai/chatbox","slug":"duplicate-file-path-for-name-resolved-norm","errorCode":null,"errorMessage":"duplicate file path for \"${name}\": ${resolved.normalizedPath}","messagePattern":"duplicate file path for \"(.+?)\": (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/skills/builtin-sync.ts","lineNumber":183,"sourceCode":"  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,\n  body: string,\n  files: RemoteSkillFile[] = [],\n  options: { replaceDir?: boolean } = {}\n): void {\n  const skillDir = path.join(getBuiltinSkillsDir(), name)\n  const fileTargets = prepareSnapshotFiles(name, skillDir, files)\n  if (options.replaceDir) {","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/builtin-sync.ts#L165-L201","documentation":"Thrown by prepareSnapshotFiles when two entries in the files array normalize to the same path (e.g. 'a/b' and 'a/./b', or 'a\\\\b' vs 'a/b' after splitting path.sep and joining '/'). seenPaths is a Set keyed by normalizedPath, so the second occurrence aborts the snapshot write to prevent overwriting ambiguity.","triggerScenarios":"Remote skill manifest lists the same logical file twice with syntactically different paths; case-insensitive filesystem collision (the normalizer does not lowercase, so 'A.md' and 'a.md' collide only on disk, not here — but 'a/b' and 'a/./b' or mixed separators do collide here).","commonSituations":"Manifest generated by merging two sources without dedupe; 'folder/file' and 'folder\\file' both present on a cross-platform skill; redundant index files; server-side packaging bug.","solutions":["Dedupe the files array by normalized path before sending/persisting the manifest.","Treat the duplicate as a server bug: log the offending normalizedPath and reject the skill.","If duplicates are benign, keep the first and skip subsequent entries instead of throwing.","Canonicalize paths (strip './', normalize separators) at manifest build time."],"exampleFix":"// before\nif (seenPaths.has(resolved.normalizedPath)) throw new Error(`duplicate file path for \"${name}\": ${resolved.normalizedPath}`)\n\n// after: last-writer-wins with a warning\nif (seenPaths.has(resolved.normalizedPath)) { log.warn(`duplicate file path for \"${name}\": ${resolved.normalizedPath}`); continue }","handlingStrategy":"validation","validationCode":"const seen = new Set<string>()\nfor (const f of files) { const n = f.path.split(path.sep).join('/'); if (seen.has(n)) { dedupeOrReject(n) } else seen.add(n) }","typeGuard":"function isDuplicateFilePath(e: unknown): e is Error { return e instanceof Error && e.message.startsWith('duplicate file path for') }","tryCatchPattern":"try { await syncSkill(name, files) } catch (e) { if (isDuplicateFilePath(e)) { files = dedupeByNormalizedPath(files); await syncSkill(name, files); return } throw e }","preventionTips":["Dedupe the manifest by normalized path before publishing.","Canonicalize separators ('/' everywhere) at build time.","Decide policy (reject vs last-wins) explicitly; don't let the throw surprise users."],"tags":["skills","builtin-sync","validation","dedupe","typescript"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}