{"record":{"id":"6ed78118f2e23e50","repo":"can1357/oh-my-pi","slug":"archive-file-normalizedpath-not-found","errorCode":null,"errorMessage":"Archive file '${normalizedPath}' not found","messagePattern":"Archive file '(.+?)' not found","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/reader.ts","lineNumber":139,"sourceCode":"\t\treturn [...children.values()].sort((left, right) =>\n\t\t\tleft.name.toLowerCase().localeCompare(right.name.toLowerCase()),\n\t\t);\n\t}\n\n\t/** Extract one file member's bytes, following symlink aliases. */\n\tasync readFile(subPath: string): Promise<ExtractedArchiveFile> {\n\t\tconst normalizedPath = normalizeArchiveLookupPath(subPath);\n\t\tif (!normalizedPath) {\n\t\t\tthrow new ArchiveError(\"Archive file path is required\");\n\t\t}\n\n\t\tconst resolvedPath = resolveArchiveLinkPath(this.#entries, normalizedPath, this.limits.maxLinkDepth);\n\t\tif (resolvedPath === \"\") {\n\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' is a directory`);\n\t\t}\n\t\tconst entry = this.#entries.get(resolvedPath);\n\t\tif (!entry) {\n\t\t\tthrow new ArchiveError(`Archive file '${normalizedPath}' not found`);\n\t\t}\n\t\tif (entry.isDirectory) {\n\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' is a directory`);\n\t\t}\n\t\tif (!entry.storage) {\n\t\t\tthrow new ArchiveError(`Archive file '${normalizedPath}' has no readable storage`);\n\t\t}\n\t\tassertArchiveMemberSize(entry.size, normalizedPath, this.limits);\n\n\t\tif (entry.storage.type === \"link\") {\n\t\t\tthrowUnreadableArchiveLink(entry.storage.targetPath, normalizedPath);\n\t\t}\n\t\tconst bytes = await entry.storage.source.read(entry.size, normalizedPath);\n\t\treturn {\n\t\t\tpath: normalizedPath,\n\t\t\tisDirectory: false,\n\t\t\tsize: entry.size,\n\t\t\tmtimeMs: entry.mtimeMs,","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/reader.ts#L121-L157","documentation":"`readFile` resolved the link path successfully, but no entry exists at that resolved path in the archive's entry map, so it throws ArchiveError(`Archive file '${normalizedPath}' not found`). Unlike error 3493 (unmaterializable link), here the lookup itself came up empty — the file simply is not in the archive (packages/utils/src/ar/reader.ts:139).","triggerScenarios":"Calling `reader.readFile('missing.txt')` with a path absent from the entry map; case mismatches ('Readme.md' vs 'readme.md'); separators or leading './' not matching stored entry paths; symlink resolving to a path that was never archived.","commonSituations":"Scripts hard-coding filenames that differ from the actual archive contents; archives regenerated with changed layout while consumers kept old paths; case-sensitivity surprises when archives made on case-insensitive systems are read case-sensitively; typos.","solutions":["Dump the real entry list first: `reader.allEntries().map(e => e.path)` and confirm the exact stored path and casing.","Normalize the path before lookup: forward slashes, no leading './' or '/', no trailing slash.","Implement a case-insensitive fallback: find an entry whose lowercase path matches when the exact lookup fails.","Catch ArchiveError with 'not found' and offer the user a fuzzy/closest-match list of archive entries."],"exampleFix":"// before: brittle exact lookup\nconst file = await reader.readFile('Config.INI');\n\n// after: exact, then case-insensitive fallback\nconst all = reader.allEntries();\nconst hit = all.find(e => e.path === 'Config.INI')\n  ?? all.find(e => e.path.toLowerCase() === 'config.ini' && !e.isDirectory);\nif (!hit) throw new Error(`File not in archive; available: ${all.map(e => e.path).join(', ')}`);\nconst file = await reader.readFile(hit.path);","handlingStrategy":"validation","validationCode":"// Confirm the exact stored path before reading\nconst all = reader.allEntries();\nconst wanted = 'Config.INI';\nconst hit = all.find(e => e.path === wanted) ?? all.find(e => !e.isDirectory && e.path.toLowerCase() === wanted.toLowerCase());\nif (!hit) throw new Error(`File not in archive: ${wanted}`);\nawait reader.readFile(hit.path);","typeGuard":"function hasFile(entries: { path: string; isDirectory: boolean }[], p: string): boolean {\n  return entries.some(e => !e.isDirectory && e.path === p);\n}","tryCatchPattern":"try {\n  return await reader.readFile(p);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.endsWith('not found')) {\n    const suggestions = reader.allEntries()\n      .filter(e => e.path.toLowerCase().includes(path.posix.basename(p).toLowerCase()))\n      .map(e => e.path);\n    throw new Error(`'${p}' not in archive. Close matches: ${suggestions.join(', ') || 'none'}`);\n  }\n  throw err;\n}","preventionTips":["Source paths from allEntries() listings, not hard-coded literals.","Match stored casing exactly; add a case-insensitive fallback for robustness.","Normalize paths (forward slashes, no './' or leading '/') before lookup.","Re-validate hard-coded paths whenever archives are regenerated with a new layout."],"tags":["archive","not-found","path"],"backgroundTag":"file-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}