{"record":{"id":"44d2981d89225c16","repo":"charmbracelet/vhs","slug":"file-srcpath-not-found","errorCode":null,"errorMessage":"File ${srcPath} not found","messagePattern":"File (.+?) not found","errorType":"validation","errorClass":"parser.Error","httpStatus":null,"severity":"error","filePath":"parser/parser.go","lineNumber":694,"sourceCode":"\t\tp.errors = append(p.errors, NewError(p.cur, \"Expected path after Source\"))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\tsrcPath := p.peek.Literal\n\n\t// Check if path has .tape extension\n\text := filepath.Ext(srcPath)\n\tif ext != \".tape\" {\n\t\tp.errors = append(p.errors, NewError(p.peek, \"Expected file with .tape extension\"))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\t// Check if tape exist\n\tif _, err := os.Stat(srcPath); os.IsNotExist(err) { //nolint:gosec\n\t\tnotFoundErr := fmt.Sprintf(\"File %s not found\", srcPath)\n\t\tp.errors = append(p.errors, NewError(p.peek, notFoundErr))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\t// Check if source tape contains nested Source command\n\td, err := os.ReadFile(srcPath) //nolint:gosec\n\tif err != nil {\n\t\treadErr := fmt.Sprintf(\"Unable to read file: %s\", srcPath)\n\t\tp.errors = append(p.errors, NewError(p.peek, readErr))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\tsrcTape := string(d)\n\t// Check source tape is NOT empty\n\tif len(srcTape) == 0 {\n\t\treadErr := fmt.Sprintf(\"Source tape: %s is empty\", srcPath)\n\t\tp.errors = append(p.errors, NewError(p.peek, readErr))","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/charmbracelet/vhs/blob/c073383b5de0b1f57bf514113029c306bc986539/parser/parser.go#L676-L712","documentation":"Fires in parseSource when os.Stat reports the referenced .tape file does not exist (os.IsNotExist). The Source command cannot be resolved, the error is recorded, and the parser moves on without including the file's commands.","triggerScenarios":"Parse() on a tape with `Source \"nested.tape\"` where nested.tape is absent from the working directory (or the path is relative to the CWD, not the parent tape's directory).","commonSituations":"Running vhs from a different directory than where the tape lives; CI checkouts that omit the nested tape; typos in the path; case-sensitive filesystems.","solutions":["Fix the path — note it is resolved relative to the process working directory, not the parent tape","Create the missing .tape file","Remove the Source line if the file is not needed"],"exampleFix":"// before\nSource \"nested.tape\"  (file absent)\n// after\nmkdir -p demo && move nested.tape demo/nested.tape; Source \"demo/nested.tape\"","handlingStrategy":"validation","validationCode":"p := \"nested.tape\"\nif _, err := os.Stat(p); os.IsNotExist(err) {\n    return fmt.Errorf(\"Source target %q does not exist (cwd=%s)\", p, mustCwd())\n}","typeGuard":"func tapeExists(path string) bool {\n    info, err := os.Stat(path)\n    return err == nil && !info.IsDir()\n}","tryCatchPattern":"if err := parse(tape); err != nil && strings.Contains(err.Error(), \"not found\") {\n    return fmt.Errorf(\"missing include referenced by %s: %w\", tape, err)\n}","preventionTips":["Run vhs from the directory containing all tapes, or use absolute paths","Commit nested tapes to the repo/CI checkout","Check path spelling and case on case-sensitive filesystems"],"tags":["filesystem","source","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"c073383b5de0b1f57bf514113029c306bc986539","analyzedAt":"2026-09-02T01:53:05.682Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}