{"record":{"id":"35c554cc45e6bd4a","repo":"can1357/oh-my-pi","slug":"archive-path-normalizedpath-is-not-a-director","errorCode":null,"errorMessage":"Archive path '${normalizedPath}' is not a directory","messagePattern":"Archive path '(.+?)' is not a directory","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/reader.ts","lineNumber":88,"sourceCode":"\t}\n\n\t/** List one directory's children, sorted case-insensitively by name. */\n\tlistDirectory(subPath?: string): ArchiveDirectoryEntry[] {\n\t\tconst normalizedPath = normalizeArchiveLookupPath(subPath);\n\t\tif (normalizedPath === undefined) {\n\t\t\tthrow new ArchiveError(\"Archive path cannot contain '..'\");\n\t\t}\n\n\t\tconst resolvedPath = normalizedPath\n\t\t\t? resolveArchiveLinkPath(this.#entries, normalizedPath, this.limits.maxLinkDepth)\n\t\t\t: \"\";\n\t\tif (normalizedPath && resolvedPath !== \"\") {\n\t\t\tconst entry = this.#entries.get(resolvedPath);\n\t\t\tif (!entry) {\n\t\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' not found`);\n\t\t\t}\n\t\t\tif (!entry.isDirectory) {\n\t\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' is not a directory`);\n\t\t\t}\n\t\t}\n\n\t\tconst sourcePrefix = resolvedPath ? `${resolvedPath}/` : \"\";\n\t\tconst children = new Map<string, ArchiveDirectoryEntry>();\n\n\t\tfor (const entry of this.#entries.values()) {\n\t\t\tif (resolvedPath) {\n\t\t\t\tif (!entry.path.startsWith(sourcePrefix) || entry.path === resolvedPath) continue;\n\t\t\t}\n\n\t\t\tconst relativePath = resolvedPath ? entry.path.slice(sourcePrefix.length) : entry.path;\n\t\t\tconst nextSegment = relativePath.split(\"/\")[0];\n\t\t\tif (!nextSegment) continue;\n\n\t\t\tconst childPath = normalizedPath ? `${normalizedPath}/${nextSegment}` : nextSegment;\n\t\t\tif (children.has(childPath)) continue;\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/reader.ts#L70-L106","documentation":"`listDirectory` found an entry at the resolved path, but it is a regular file (or other non-directory member), so it throws ArchiveError(`Archive path '${normalizedPath}' is not a directory`). Directory listing only works on entries flagged isDirectory (packages/utils/src/ar/reader.ts:88).","triggerScenarios":"Calling `reader.listDirectory('file.txt')` where that path maps to a file entry; passing a symlink that resolves to a file instead of a directory; confusing the file/directory listing APIs (readFile vs listDirectory) with the same path.","commonSituations":"Code that walks archives generically and calls listDirectory on every entry without checking isDirectory; paths that look like directories because of extensions but are files; API misuse where readFile should have been used.","solutions":["Check `entry.isDirectory` from allEntries() before calling listDirectory; use readFile for file entries instead.","If you want the parent folder of a file, derive it (strip the last path segment) and list that directory.","Catch this ArchiveError and dispatch to readFile when the path turns out to be a file.","For symlink paths, resolve intent first: inspect the entry's target and its isDirectory flag."],"exampleFix":"// before: same handler for any path\nconst kids = reader.listDirectory(targetPath);\n\n// after: branch on entry type\nconst entry = reader.allEntries().find(e => e.path === targetPath);\nif (entry && !entry.isDirectory) {\n  const file = await reader.readFile(targetPath);\n} else {\n  const kids = reader.listDirectory(targetPath);\n}","handlingStrategy":"type-guard","validationCode":"// Check entry kind before choosing the API\nconst entry = reader.allEntries().find(e => e.path === targetPath);\nif (entry && !entry.isDirectory) {\n  await reader.readFile(targetPath); // file path\n} else {\n  reader.listDirectory(targetPath); // directory path\n}","typeGuard":"function isFileEntry(\n  entries: { path: string; isDirectory: boolean }[],\n  p: string,\n): boolean {\n  const e = entries.find(x => x.path === p);\n  return e !== undefined && !e.isDirectory;\n}","tryCatchPattern":"try {\n  return reader.listDirectory(p);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.endsWith('is not a directory')) {\n    return { kind: 'file' as const, read: () => reader.readFile(p) };\n  }\n  throw err;\n}","preventionTips":["Branch on entry.isDirectory before calling listDirectory vs readFile.","If the caller wants a file's folder, derive the parent path (strip last segment) and list that.","Do not assume trailing-slash-less names are files; check the entry map.","Centralize this dispatch in one helper so callers cannot pick the wrong API."],"tags":["archive","wrong-type","path"],"backgroundTag":"not-a-directory","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}