charmbracelet/vhs · error · parser.Error
Expected file with .tape extension
Error message
Expected file with .tape extension
What it means
Fires in parseSource when the path passed to the Source command does not have a .tape extension. The parser refuses to include files that are not tape files, emits the error, advances past the token, and returns without loading the file.
Source
Thrown at parser/parser.go:686
// parseSource parses source command.
// Source command takes a tape path to include in current tape.
//
// Source <path>
func (p *Parser) parseSource() []Command {
cmd := Command{Type: token.SOURCE}
if p.peek.Type != token.STRING {
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()View on GitHub (pinned to c073383b5d)
Solutions
- Rename the included file to have a .tape extension
- Point Source at the correct .tape file
- Convert the file's content into a .tape file
Example fix
// before Source "settings.yml" // after Source "settings.tape"
Defensive patterns
Strategy: validation
Validate before calling
p := "partials/settings.tape"
if filepath.Ext(p) != ".tape" {
return fmt.Errorf("Source target %q must have .tape extension", p)
} Type guard
func isTapeFile(path string) bool { return filepath.Ext(path) == ".tape" } Try / catch
if err := parse(tape); err != nil && strings.Contains(err.Error(), ".tape extension") {
return fmt.Errorf("rename include to .tape: %w", err)
} Prevention
- Name all includable files with .tape extension
- Watch for hidden double extensions from editors
- Centralize include paths as constants ending in .tape
When it happens
Trigger: Calling Parse() on a tape with `Source "settings.yml"`, `Source "script.sh"`, or a file with no extension — ext != ".tape" triggers the error before any file system access.
Common situations: Trying to source config files, shell scripts, or YAML; typos like `.taep` or `.tape.txt` (hidden double extension); macOS/editor appending extensions.
Related errors
- Expected file with .png extension
- Source tape: ${srcPath} is empty
- Invalid command: ${literal}
- Wait+ expects Line or Screen
- Wait expects positive duration
AI-assisted analysis of charmbracelet/vhs@c073383b5d (2026-09-02).
Data as JSON: /api/errors/15a93b299cdae561.
Report an issue: GitHub.