chatboxai/chatbox · warning · Error

duplicate file path for "${name}": ${resolved.normalizedPath

Error message

duplicate file path for "${name}": ${resolved.normalizedPath}

What it means

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.

Source

Thrown at src/main/skills/builtin-sync.ts:183

  const target = path.resolve(skillDir, normalized)
  const root = path.resolve(skillDir)
  if (target !== root && !target.startsWith(root + path.sep)) return null
  return { target, normalizedPath: normalized.split(path.sep).join('/') }
}

function prepareSnapshotFiles(name: string, skillDir: string, files: RemoteSkillFile[]): SnapshotFileTarget[] {
  const targets: SnapshotFileTarget[] = []
  const seenPaths = new Set<string>()
  for (const file of files) {
    if (!file || typeof file.path !== 'string' || typeof file.content !== 'string') {
      throw new Error(`invalid file entry for "${name}"`)
    }
    const resolved = resolveSnapshotFilePath(skillDir, file.path)
    if (!resolved) {
      throw new Error(`invalid file path for "${name}": ${file.path}`)
    }
    if (seenPaths.has(resolved.normalizedPath)) {
      throw new Error(`duplicate file path for "${name}": ${resolved.normalizedPath}`)
    }
    seenPaths.add(resolved.normalizedPath)
    targets.push({ file, target: resolved.target, normalizedPath: resolved.normalizedPath })
  }
  return targets
}

/** 将 skill 内容写入快照目录的 SKILL.md(frontmatter + body)和附属文件,与 parser 的解析格式一致。 */
function writeSnapshotSkill(
  name: string,
  metadata: SkillMetadata,
  body: string,
  files: RemoteSkillFile[] = [],
  options: { replaceDir?: boolean } = {}
): void {
  const skillDir = path.join(getBuiltinSkillsDir(), name)
  const fileTargets = prepareSnapshotFiles(name, skillDir, files)
  if (options.replaceDir) {

View on GitHub (pinned to 81571269ad)

Solutions

  1. Dedupe the files array by normalized path before sending/persisting the manifest.
  2. Treat the duplicate as a server bug: log the offending normalizedPath and reject the skill.
  3. If duplicates are benign, keep the first and skip subsequent entries instead of throwing.
  4. Canonicalize paths (strip './', normalize separators) at manifest build time.

Example fix

// before
if (seenPaths.has(resolved.normalizedPath)) throw new Error(`duplicate file path for "${name}": ${resolved.normalizedPath}`)

// after: last-writer-wins with a warning
if (seenPaths.has(resolved.normalizedPath)) { log.warn(`duplicate file path for "${name}": ${resolved.normalizedPath}`); continue }
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set<string>()
for (const f of files) { const n = f.path.split(path.sep).join('/'); if (seen.has(n)) { dedupeOrReject(n) } else seen.add(n) }

Type guard

function isDuplicateFilePath(e: unknown): e is Error { return e instanceof Error && e.message.startsWith('duplicate file path for') }

Try / catch

try { await syncSkill(name, files) } catch (e) { if (isDuplicateFilePath(e)) { files = dedupeByNormalizedPath(files); await syncSkill(name, files); return } throw e }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/e18dbfedc784823b. Report an issue: GitHub.