charmbracelet/vhs · error · parser.Error
File ${srcPath} not found
Error message
File ${srcPath} not found What it means
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.
Source
Thrown at parser/parser.go:694
p.errors = append(p.errors, NewError(p.cur, "Expected path after Source"))
p.nextToken()
return []Command{cmd}
}
srcPath := p.peek.Literal
// Check if path has .tape extension
ext := filepath.Ext(srcPath)
if ext != ".tape" {
p.errors = append(p.errors, NewError(p.peek, "Expected file with .tape extension"))
p.nextToken()
return []Command{cmd}
}
// Check if tape exist
if _, err := os.Stat(srcPath); os.IsNotExist(err) { //nolint:gosec
notFoundErr := fmt.Sprintf("File %s not found", srcPath)
p.errors = append(p.errors, NewError(p.peek, notFoundErr))
p.nextToken()
return []Command{cmd}
}
// Check if source tape contains nested Source command
d, err := os.ReadFile(srcPath) //nolint:gosec
if err != nil {
readErr := fmt.Sprintf("Unable to read file: %s", srcPath)
p.errors = append(p.errors, NewError(p.peek, readErr))
p.nextToken()
return []Command{cmd}
}
srcTape := string(d)
// Check source tape is NOT empty
if len(srcTape) == 0 {
readErr := fmt.Sprintf("Source tape: %s is empty", srcPath)
p.errors = append(p.errors, NewError(p.peek, readErr))View on GitHub (pinned to c073383b5d)
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
Example fix
// before Source "nested.tape" (file absent) // after mkdir -p demo && move nested.tape demo/nested.tape; Source "demo/nested.tape"
Defensive patterns
Strategy: validation
Validate before calling
p := "nested.tape"
if _, err := os.Stat(p); os.IsNotExist(err) {
return fmt.Errorf("Source target %q does not exist (cwd=%s)", p, mustCwd())
} Type guard
func tapeExists(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir()
} Try / catch
if err := parse(tape); err != nil && strings.Contains(err.Error(), "not found") {
return fmt.Errorf("missing include referenced by %s: %w", tape, err)
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- Unable to read file: ${srcPath}
- Expected path after Source
- Nested Source detected
- ${srcPath} has ${count} errors
AI-assisted analysis of charmbracelet/vhs@c073383b5d (2026-09-02).
Data as JSON: /api/errors/44d2981d89225c16.
Report an issue: GitHub.