{"record":{"id":"f7f8d1f6dbf2bd62","repo":"can1357/oh-my-pi","slug":"invalid-zip-archive-symlink-has-no-payload","errorCode":null,"errorMessage":"Invalid ZIP archive: symlink has no payload","messagePattern":"Invalid ZIP archive: symlink has no payload","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/zip.ts","lineNumber":554,"sourceCode":"\t\t\t};\n\t\t\tparsed.push({ entry, isSymlink });\n\t\t\tassertEntryCount(parsed.length, options.limits);\n\t\t}\n\t\toffset = end;\n\t}\n\treturn parsed;\n}\n\nasync function readZipImpl(source: ByteSource, options: FormatReadOptions): Promise<ArchiveIndexEntry[]> {\n\tconst info = await readCentralDirectoryInfo(source, options.limits);\n\tif (info.size === 0) return [];\n\tconst directory = await source.read(info.offset, info.offset + info.size);\n\tif (directory.byteLength !== info.size) throw new ArchiveError(\"Invalid ZIP archive: truncated central directory\");\n\tconst parsed = parseCentralDirectory(source, directory, info, options);\n\tfor (const item of parsed) {\n\t\tif (!item.isSymlink) continue;\n\t\tassertArchivePathBytes(item.entry.size, \"symlink target\", options.limits.maxPathBytes);\n\t\tif (item.entry.storage?.type !== \"member\") throw new ArchiveError(\"Invalid ZIP archive: symlink has no payload\");\n\t\tconst bytes = await item.entry.storage.source.read(item.entry.size, item.entry.path);\n\t\tconst target = UTF8_DECODER.decode(bytes);\n\t\tassertArchivePathString(target, \"symlink target\", options.limits.maxPathBytes);\n\t\tconst portable = target.replace(/\\\\/g, \"/\");\n\t\tconst absolute = path.posix.isAbsolute(portable) || /^[A-Za-z]:/.test(portable) || portable.includes(\"\\0\");\n\t\tconst targetPath = absolute\n\t\t\t? undefined\n\t\t\t: normalizeArchiveLookupPath(path.posix.join(path.posix.dirname(item.entry.path), portable));\n\t\titem.entry.size = 0;\n\t\titem.entry.storage = {\n\t\t\ttype: \"link\",\n\t\t\ttargetPath: targetPath === undefined ? portable : targetPath,\n\t\t\tresolveTarget: targetPath !== undefined,\n\t\t};\n\t}\n\treturn parsed.map(item => item.entry);\n}\n","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/zip.ts#L536-L572","documentation":"Thrown when a central-directory entry is detected as a symlink but its storage type is not a regular member payload, so the library cannot read the symlink target bytes to validate them. It refuses to construct an entry with an unverifiable target.","triggerScenarios":"Reading a ZIP containing a symlink entry (external attributes mark it as a symlink) whose storage is missing or of a non-member type — typically a corrupt entry, a directory placeholder, or a zero-size/dangling symlink record.","commonSituations":"ZIPs of Unix directory trees created by tools that mishandle symlinks (some tar-to-zip converters, misconfigured CI packaging); archives truncated or patched so the symlink's data offset no longer resolves.","solutions":["Recreate the archive with a zip tool that stores symlinks correctly (e.g. `zip -ry archive.zip dir` preserves symlinks)","Inspect the archive with `unzip -v` / 7-Zip to find the malformed symlink entry and repair or drop it","If you only need regular files, filter out symlinks before/while reading if the API allows, or extract with a tolerant tool first"],"exampleFix":"// before: zipping with a tool that breaks symlinks\nawait $`zip -r app.zip app`;\n// after: preserve symlinks recursively\nawait $`zip -ry app.zip app`;","handlingStrategy":"try-catch","validationCode":"const entries = await listZip(file); // if listing API available\nconst bad = entries.filter(e => e.isSymlink && (e.size === 0 || e.storageType !== 'member'));\nif (bad.length) throw new Error(`malformed symlinks: ${bad.map(b => b.path).join(', ')}`);","typeGuard":null,"tryCatchPattern":"try {\n  return await readZip(file);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('symlink has no payload')) {\n    logger.warn('skipping archive with malformed symlink entries', { path: file.name });\n    return null;\n  }\n  throw err;\n}","preventionTips":["Create archives with symlink-preserving flags (zip -ry)","Avoid tar-to-zip converters that drop symlink payloads","Inspect archives containing symlinks with unzip -v before automated processing"],"tags":["zip","symlink","archive","corrupt-file"],"backgroundTag":"invalid-zip-symlink-entry","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}