{"record":{"id":"14b2682608a57616","repo":"heygen-com/hyperframes","slug":"invalid-project-directory","errorCode":null,"errorMessage":"Invalid project directory: #","messagePattern":"Invalid project directory: #","errorType":"validation","errorClass":"InvalidProjectError","httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/project.ts","lineNumber":37,"sourceCode":"  readonly hint?: string;\n  readonly suggestion?: string;\n\n  constructor(title: string, hint?: string, suggestion?: string) {\n    super(title);\n    this.name = \"InvalidProjectError\";\n    this.title = title;\n    this.hint = hint;\n    this.suggestion = suggestion;\n  }\n}\n\nexport function resolveProjectOrThrow(\n  dirArg: string | undefined,\n  options: ResolveProjectOptions = {},\n): ProjectDir {\n  const trimmed = dirArg?.trim();\n  if (trimmed === \"#\") {\n    throw new InvalidProjectError(\n      \"Invalid project directory: #\",\n      \"# is a URL fragment, not a project path.\",\n      \"Run hyperframes preview . from your project directory.\",\n    );\n  }\n\n  const dir = resolve(dirArg ?? \".\");\n  const name = basename(dir);\n  const indexPath = resolve(dir, \"index.html\");\n\n  if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n    throw new InvalidProjectError(\"Not a directory: \" + dir);\n  }\n  if (options.requireIndex !== false && !existsSync(indexPath)) {\n    throw new InvalidProjectError(\n      \"No composition found in \" + dir,\n      \"No index.html file found.\",\n      \"Run npx hyperframes init to create a new composition.\",","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/project.ts#L19-L55","documentation":"resolveProjectOrThrow special-cases dirArg === '#' (after trim). '#' is a URL fragment, not a filesystem path — it appears when a user pastes a URL or a shell-quoting artifact where the project directory argument should go. The library throws InvalidProjectError with a targeted hint and suggestion rather than letting it fall through to the generic 'Not a directory' check, because the '#' case is a known copy-paste footgun.","triggerScenarios":"Running a CLI command with '#' as the project argument: `hyperframes preview #`, or a shell-expanded variable that resolved to '#', or pasting a URL whose fragment leaked into argv. Also when a script passes through an unescaped anchor reference.","commonSituations":"User copies a URL like https://example.com/composition# and the trailing # lands as argv; a bash history substitution gone wrong (!# expansion); an empty arg that defaulted to '#' in a wrapper script; CI config that echoes a URL fragment into the project slot.","solutions":["Run from inside your project directory with `.` or no argument: `hyperframes preview .`.","Pass the actual project path: `hyperframes preview /path/to/project`.","If the '#' came from a URL, strip everything from '#' onwards before passing it as a path.","Audit wrapper scripts for unquoted variable expansions that could yield '#'."],"exampleFix":"# before\nhyperframes preview #\n# after\nhyperframes preview .   # from inside the project directory","handlingStrategy":"validation","validationCode":"function isLikelyValidProjectArg(v: string | undefined): boolean {\n  const t = v?.trim();\n  return t !== undefined && t !== '' && t !== '#';\n}\n\nif (!isLikelyValidProjectArg(process.argv[2])) {\n  throw new Error('Pass a project directory (or `.`); got: ' + process.argv[2]);\n}","typeGuard":"function isNonFragmentPath(v: unknown): v is string {\n  return typeof v === 'string' && v.trim() !== '' && v.trim() !== '#';\n}","tryCatchPattern":"try {\n  return resolveProjectOrThrow(dirArg);\n} catch (err) {\n  if (err instanceof InvalidProjectError && /Invalid project directory: #/.test(err.message)) {\n    console.error(err.hint, err.suggestion);\n  } else throw err;\n}","preventionTips":["Quote shell variables to prevent '#' leakage from URLs/history expansion.","Default the project arg to '.' in wrapper scripts rather than forwarding raw user input.","Strip URL fragments (#...) from pasted paths before passing them to the CLI."],"tags":["cli","project","validation","argument","url-fragment"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}