charmbracelet/vhs · error · parser.Error

Source tape: is empty

Error message

Source tape: ${srcPath} is empty

What it means

After reading the file, parseSource checks len(srcTape) == 0 and reports "Source tape: <path> is empty" if the included tape has no content. An empty include contributes nothing and is treated as a mistake.

Solutions

  1. Add valid tape instructions to the sourced file
  2. Remove the Source line if the include is not needed
  3. Verify file size with `wc -c` / `ls -l` before running

Example fix

// before
(empty file)
// after
echo 'Type "hello"' > empty.tape
Defensive patterns

Strategy: validation

Validate before calling

p := "nested.tape"
info, err := os.Stat(p)
if err == nil && info.Size() == 0 {
    return fmt.Errorf("Source target %s is empty", p)
}

Type guard

func tapeNonEmpty(path string) bool {
    info, err := os.Stat(path)
    return err == nil && info.Size() > 0
}

Try / catch

if err := parse(tape); err != nil && strings.Contains(err.Error(), "is empty") {
    return fmt.Errorf("populate or remove empty include: %w", err)
}

Prevention

When it happens

Trigger: Parse() on a tape sourcing a zero-byte .tape file (e.g. `touch empty.tape` then `Source "empty.tape"`), or a file truncated by a failed checkout/save.

Common situations: Placeholder files created but never filled in; interrupted writes; git LFS files not materialized (0 bytes).

Related errors


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

Appendix: source

Thrown at parser/parser.go:712

		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))
		p.nextToken()
		return []Command{cmd}
	}

	srcLexer := lexer.New(srcTape)
	srcParser := New(srcLexer)

	// Check not nested source
	srcCmds := srcParser.Parse()
	for _, cmd := range srcCmds {
		if cmd.Type == token.SOURCE {
			p.errors = append(p.errors, NewError(p.peek, "Nested Source detected"))
			p.nextToken()
			return []Command{cmd}
		}
	}

	// Check src errors

View on GitHub (pinned to c073383b5d)