{"record":{"id":"ef6b8c68eb8891aa","repo":"angular/angular-cli","slug":"path-p-is-a-file","errorCode":null,"errorMessage":"Path \"${p}\" is a file.","messagePattern":"Path \"(.+?)\" is a file\\.","errorType":"exception","errorClass":"PathIsFileException","httpStatus":null,"severity":"error","filePath":"packages/angular_devkit/schematics/src/tree/host-tree.ts","lineNumber":357,"sourceCode":"    return this._recordSync.isFile(this._normalizePath(path));\n  }\n\n  get(path: string): FileEntry | null {\n    const p = this._normalizePath(path);\n    if (this._recordSync.isDirectory(p)) {\n      throw new PathIsDirectoryException(p);\n    }\n    if (!this._recordSync.exists(p)) {\n      return null;\n    }\n\n    return new LazyFileEntry(p, () => Buffer.from(this._recordSync.read(p)));\n  }\n\n  getDir(path: string): DirEntry {\n    const p = this._normalizePath(path);\n    if (this._recordSync.isFile(p)) {\n      throw new PathIsFileException(p);\n    }\n\n    let maybeCache = this._dirCache.get(p);\n    if (!maybeCache) {\n      let parent: Path | null = dirname(p);\n      if (p === parent) {\n        parent = null;\n      }\n\n      maybeCache = new HostDirEntry(parent && this.getDir(parent), p, this._recordSync, this);\n      this._dirCache.set(p, maybeCache);\n    }\n\n    return maybeCache;\n  }\n  visit(visitor: FileVisitor): void {\n    this.root.visit((path, entry) => {\n      visitor(path, entry);","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular_devkit/schematics/src/tree/host-tree.ts#L339-L375","documentation":"HostTree.getDir(path) returns the DirEntry for a path, but throws PathIsFileException when the path actually points to a file. Directory traversal APIs (dir(), subDirs, root) route through getDir, so the same throw occurs when one of those receives a file path.","triggerScenarios":"Calling tree.getDir(path), tree.dir(path), or dirEntry.subDir(...) where the path is an existing file; navigating with a segment that accidentally matches a file name (e.g. a file named 'src' or 'app').","commonSituations":"Building paths from user config where a value was intended to be a folder; projects that have files where directories were expected; off-by-one path joins that drop the final segment.","solutions":["Verify the path is a directory before calling getDir: if (tree.exists(p)) it's a file — use tree.get(p) instead.","Print/log the resolved path; fix the path construction so it targets a directory.","If files named like directories are legal, branch on tree.exists() before choosing get vs getDir."],"exampleFix":"// before\nconst dir = tree.getDir(targetPath);\n// after\nconst dir = tree.exists(targetPath) ? undefined : tree.getDir(targetPath);\nif (!dir) {\n  throw new Error(`${targetPath} is a file, expected a directory`);\n}","handlingStrategy":"validation","validationCode":"if (tree.exists(dirPath)) {\n  throw new Error(`${dirPath} is a file, expected a directory`);\n}","typeGuard":"function isDirectory(tree: Tree, path: string): boolean {\n  return !tree.exists(path); // not a file; combined with getDir success means directory\n}","tryCatchPattern":"let dir;\ntry {\n  dir = tree.getDir(p);\n} catch (e) {\n  if ((e as any).constructor?.name === 'PathIsFileException') {\n    // treat as file: tree.get(p)\n  } else throw e;\n}","preventionTips":["Confirm directory paths from user config end where a folder actually exists","Watch for files whose names collide with intended folder names (e.g. 'src' file)","Branch on tree.exists() before choosing get vs getDir","Use dirEntry.subDir with verified segment names"],"tags":["path","file","dir-entry","schematics"],"backgroundTag":"path-is-file","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}