{"record":{"id":"85074286bd3906a9","repo":"can1357/oh-my-pi","slug":"tar-member-formatarchivepathforerror-parent","errorCode":null,"errorMessage":"Tar member '${formatArchivePathForError(parent)}' is not a directory","messagePattern":"Tar member '(.+?)' is not a directory","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":756,"sourceCode":"export async function encodeTar(members: Iterable<readonly [string, Uint8Array]>): Promise<Uint8Array> {\n\tconst parts: Uint8Array[] = [];\n\tconst kinds = new Map<string, \"directory\" | \"file\">();\n\tlet sequence = 0;\n\tfor (const [rawPath, bytes] of members) {\n\t\tconst directory = rawPath.endsWith(\"/\") || rawPath.endsWith(\"\\\\\");\n\t\tconst normalized = normalizeArchiveEntryPath(rawPath);\n\t\tif (!normalized) throw new ArchiveError(`Invalid tar member path '${formatArchivePathForError(rawPath)}'`);\n\t\tconst pathBytes = TEXT_ENCODER.encode(normalized);\n\t\tif (pathBytes.byteLength === 0) throw new ArchiveError(\"Invalid empty tar member path\");\n\t\tif (directory && bytes.byteLength !== 0) {\n\t\t\tthrow new ArchiveError(`Tar directory '${formatArchivePathForError(normalized)}' cannot contain file data`);\n\t\t}\n\t\tconst segments = normalized.split(\"/\");\n\t\tfor (let index = 1; index < segments.length; index++) {\n\t\t\tconst parent = segments.slice(0, index).join(\"/\");\n\t\t\tconst kind = kinds.get(parent);\n\t\t\tif (kind === \"file\")\n\t\t\t\tthrow new ArchiveError(`Tar member '${formatArchivePathForError(parent)}' is not a directory`);\n\t\t\tif (kind === \"directory\") continue;\n\t\t\tkinds.set(parent, \"directory\");\n\t\t\tappendTarEntry(parts, parent, new Uint8Array(0), true, sequence++);\n\t\t}\n\t\tconst existing = kinds.get(normalized);\n\t\tif (existing) {\n\t\t\tif (directory && existing === \"directory\") continue;\n\t\t\tthrow new ArchiveError(`Duplicate tar member path '${formatArchivePathForError(normalized)}'`);\n\t\t}\n\t\tkinds.set(normalized, directory ? \"directory\" : \"file\");\n\t\tappendTarEntry(parts, normalized, bytes, directory, sequence++);\n\t}\n\tparts.push(new Uint8Array(BLOCK_SIZE * 2));\n\treturn concatBytes(parts);\n}\n","sourceCodeStart":738,"sourceCodeEnd":772,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L738-L772","documentation":"While inserting implicit parent directory entries, the encoder found a parent path already registered as a regular file. A tar path cannot be both a file and a prefix of another member, so it throws naming the offending parent.","triggerScenarios":"Members include both 'a' (a file with bytes) and 'a/b' (a file) — the encoder must synthesize directory 'a' for 'a/b' but 'a' is already a file.","commonSituations":"Merging member sets from multiple sources; keys derived from filesystem scans where 'a' is a file but a stale key 'a/b' persisted; path collisions after normalization (e.g. 'a' vs './a').","solutions":["Rename one of the conflicting members so no path is both a file and a directory prefix","Drop the file entry 'a' or nest its data at another key","Deduplicate/validate the members map keys before encoding"],"exampleFix":"// before\nmembers.set(\"a\", fileBytes);\nmembers.set(\"a/b\", otherBytes); // conflict\n// after\nmembers.set(\"a-collision\", fileBytes);\nmembers.set(\"a/b\", otherBytes);","handlingStrategy":"validation","validationCode":"const fileKeys = new Set(\n\t[...members.entries()].filter(([, b]) => b.byteLength > 0 && !/[\\\\/]$/.test(k0(b)))\n);\nfunction k0(x: unknown) { return x as string; }\nfor (const key of members.keys()) {\n\tconst segs = key.split(\"/\");\n\tfor (let i = 1; i < segs.length; i++) {\n\t\tconst parent = segs.slice(0, i).join(\"/\");\n\t\tif (fileKeys.has(parent)) throw new Error(`'${parent}' is a file but '${key}' needs it as a directory`);\n\t}\n}","typeGuard":null,"tryCatchPattern":"try {\n\treturn await createTar(members);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"is not a directory\")) {\n\t\t// rename one of the colliding members\n\t}\n\tthrow err;\n}","preventionTips":["Before encoding, verify no member path is a prefix of another member while being a file","Deduplicate keys after path normalization","Validate merged member sets from multiple sources"],"tags":["archive","tar","path-collision"],"backgroundTag":"archive-member-path-collision","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}