charmbracelet/vhs · error · parser.Error

Nested Source detected

Error message

Nested Source detected

What it means

parseSource parses the included tape with a sub-parser and scans its commands; if any command is itself a SOURCE, it reports "Nested Source detected" and aborts. This library forbids source-within-source to keep include resolution one level deep and avoid recursion.

Source

Thrown at parser/parser.go:724

	}

	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
	srcErrors := srcParser.Errors()
	if len(srcErrors) > 0 {
		p.errors = append(p.errors, NewError(p.peek, fmt.Sprintf("%s has %d errors", srcPath, len(srcErrors))))
		p.nextToken()
		return []Command{cmd}
	}

	filtered := make([]Command, 0)
	for _, srcCmd := range srcCmds {
		// Output have to be avoid in order to not overwrite output of the original tape.
		if srcCmd.Type == token.SOURCE ||
			srcCmd.Type == token.OUTPUT {

View on GitHub (pinned to c073383b5d)

Solutions

  1. Flatten the nested include: inline the grandchild tape's contents into the child tape
  2. Remove the Source line from the included tape
  3. Restructure so each top-level tape sources only leaf tapes with no Source commands

Example fix

// child.tape before
Source "base.tape"
Type "hi"
// after
(base.tape commands pasted here)
Type "hi"
Defensive patterns

Strategy: validation

Validate before calling

data, _ := os.ReadFile("child.tape")
for i, line := range strings.Split(string(data), "\n") {
    if strings.HasPrefix(strings.TrimSpace(line), "Source ") {
        return fmt.Errorf("child.tape line %d contains nested Source; flatten it", i+1)
    }
}

Type guard

func hasSourceCmd(cmds []Command) bool {
    for _, c := range cmds {
        if c.Type == token.SOURCE { return true }
    }
    return false
}

Try / catch

if err := parse(tape); err != nil && strings.Contains(err.Error(), "Nested Source") {
    return fmt.Errorf("flatten includes in %s: %w", tape, err)
}

Prevention

When it happens

Trigger: Parse() on a tape whose Source target itself contains a `Source "..."` line — the sub-parser emits a SOURCE command and the outer parser flags nesting.

Common situations: Refactoring tapes into shared partials where a partial also sources a common base; trying to build an include tree; combining generated tapes that contain Source lines.

Related errors


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