{"record":{"id":"2ceb1cc98c973c7c","repo":"can1357/oh-my-pi","slug":"asar-member-path-memberpath-crosses-file-p","errorCode":null,"errorMessage":"ASAR member path '${memberPath}' crosses file '${parts.slice(0, index + 1).join(\"/\")}'","messagePattern":"ASAR member path '(.+?)' crosses file '(.+?)'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/asar.ts","lineNumber":426,"sourceCode":"\t\tconst payloads: Uint8Array[] = [];\n\t\tlet payloadSize = 0;\n\t\tfor (const member of members) {\n\t\t\tif (\n\t\t\t\t!Array.isArray(member) ||\n\t\t\t\tmember.length !== 2 ||\n\t\t\t\ttypeof member[0] !== \"string\" ||\n\t\t\t\t!(member[1] instanceof Uint8Array)\n\t\t\t) {\n\t\t\t\tthrow new ArchiveError(\"ASAR members must be [path, Uint8Array] pairs\");\n\t\t\t}\n\t\t\tconst memberPath = writerPath(member[0]);\n\t\t\tconst parts = memberPath.split(\"/\");\n\t\t\tlet directory = root;\n\t\t\tfor (let index = 0; index < parts.length - 1; index++) {\n\t\t\t\tconst part = parts[index]!;\n\t\t\t\tconst existing = directory.files[part];\n\t\t\t\tif (existing && !(\"files\" in existing)) {\n\t\t\t\t\tthrow new ArchiveError(\n\t\t\t\t\t\t`ASAR member path '${memberPath}' crosses file '${parts.slice(0, index + 1).join(\"/\")}'`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!existing) {\n\t\t\t\t\tdirectory.files[part] = { files: Object.create(null) as Record<string, AsarNode> };\n\t\t\t\t}\n\t\t\t\tdirectory = directory.files[part] as AsarDirectoryNode;\n\t\t\t}\n\t\t\tconst name = parts.at(-1)!;\n\t\t\tif (directory.files[name]) {\n\t\t\t\tthrow new ArchiveError(`Duplicate or conflicting ASAR member path '${memberPath}'`);\n\t\t\t}\n\t\t\tif (!Number.isSafeInteger(member[1].byteLength) || !Number.isSafeInteger(payloadSize + member[1].byteLength)) {\n\t\t\t\tthrow new ArchiveError(\"ASAR payload is too large to encode safely\");\n\t\t\t}\n\t\t\tdirectory.files[name] = { size: member[1].byteLength, offset: String(payloadSize) };\n\t\t\tpayloads.push(member[1]);\n\t\t\tpayloadSize += member[1].byteLength;","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/asar.ts#L408-L444","documentation":"encodeAsar builds a tree of directories from member paths; this error fires when a path needs an intermediate segment (e.g. 'a/b.txt') but that segment was already registered as a FILE by an earlier member (e.g. 'a'). ASAR nodes cannot be both a file and a directory, so the library refuses to silently overwrite. It is a conflict between two member paths in the same encode call.","triggerScenarios":"Calling encodeAsar (directly or via encodeArchive/bytes) with two members whose paths collide at an intermediate segment: one member is a prefix-path of another, e.g. ['assets', data1] plus ['assets/logo.png', data2]. The second member's directory walk hits 'assets', finds a file node instead of a directory, and throws.","commonSituations":"Globbing directories that collect both files and their parent paths as entries; merging member lists from multiple sources where one treats 'assets' as a file and another nests content under 'assets/'; backslash-vs-slash normalization making 'a\\b' and 'a/b' look distinct to the caller but collapse to the same tree in the writer.","solutions":["Deduplicate/validate member paths before encoding: reject any path that is a strict prefix (directory-wise) of another path.","Decide which node is correct — if 'assets' was meant to be a directory, don't add it as a file member; if it's a file, rename the nested entries.","Normalize path separators before building the member list so 'a\\b' and 'a/b' don't produce conflicting shapes.","Wrap the encodeAsar call in try/catch for ArchiveError and surface the two offending paths to the user."],"exampleFix":"// before\nmembers.push(['assets', fileBytes]);\nmembers.push(['assets/logo.png', logoBytes]); // throws: crosses file 'assets'\n// after\nmembers.push(['assets/logo.png', logoBytes]); // drop the bare 'assets' file member, or nest the file as 'assets/_self'","handlingStrategy":"validation","validationCode":"function isPrefixConflict(members) {\n  const files = new Set(members.map(([p]) => p.replace(/\\\\/g, '/')));\n  for (const p of files) {\n    const parts = p.split('/');\n    for (let i = 1; i < parts.length; i++) {\n      if (files.has(parts.slice(0, i).join('/'))) return `${parts.slice(0, i).join('/')}`;\n    }\n  }\n  return null;\n}\nconst conflict = isPrefixConflict(members);\nif (conflict) throw new Error(`member '${conflict}' is both a file and a directory`);","typeGuard":null,"tryCatchPattern":"try {\n  const archive = await encodeAsar(members);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('crosses file')) {\n    throw new Error(`Conflicting ASAR paths: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Normalize backslashes to forward slashes before building member lists.","Reject any member path that is a directory prefix of another member before encoding.","Deduplicate members through a Map keyed by normalized path.","Keep glob patterns non-overlapping when collecting files."],"tags":["asar","archive","path-conflict","validation"],"backgroundTag":"asar-path-conflict","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}