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

  1. Rename the included file to have a .tape extension
  2. Point Source at the correct .tape file
  3. 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

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


AI-assisted analysis of charmbracelet/vhs@c073383b5d (2026-09-02). Data as JSON: /api/errors/15a93b299cdae561. Report an issue: GitHub.