charmbracelet/vhs · error · parser.Error

${srcPath} has ${count} errors

Error message

${srcPath} has ${count} errors

What it means

If the sub-parser produced errors while parsing the included tape, parseSource adds a single aggregate error "<path> has <n> errors" and skips the include. The detailed errors belong to the sub-parser and are not propagated individually.

Source

Thrown at parser/parser.go:733

	}

	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 {
			continue
		}
		filtered = append(filtered, srcCmd)
	}

	p.nextToken()
	return filtered
}

View on GitHub (pinned to c073383b5d)

Solutions

  1. Open and validate the referenced .tape file directly to see its actual errors
  2. Fix syntax in the child tape (quote strings, use known commands)
  3. Temporarily run the child tape on its own to surface the underlying error list

Example fix

// child.tape before
Copy setup.cfg
// after
Copy "setup.cfg"
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate the child tape before including it
child, _ := os.ReadFile("child.tape")
lp := lexer.New(string(child))
sp := New(lp)
sp.Parse()
if n := len(sp.Errors()); n > 0 {
    return fmt.Errorf("child.tape invalid: %d errors", n)
}

Type guard

func childTapeParses(path string) bool {
    d, err := os.ReadFile(path)
    if err != nil { return false }
    sp := New(lexer.New(string(d)))
    sp.Parse()
    return len(sp.Errors()) == 0
}

Try / catch

if err := parse(tape); err != nil && strings.Contains(err.Error(), "has") && strings.Contains(err.Error(), "errors") {
    return fmt.Errorf("fix the included tape first: %w", err)
}

Prevention

When it happens

Trigger: Parse() on a tape whose Source target contains any invalid syntax (missing quotes, unknown commands, etc.) — srcParser.Errors() is non-empty, so the outer parser reports the count instead of parsing further.

Common situations: Sourcing a hand-edited tape with typos; a child tape written for an older/newer syntax version; copy-pasted instructions that the lexer cannot tokenize.

Related errors


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