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
- Open and validate the referenced .tape file directly to see its actual errors
- Fix syntax in the child tape (quote strings, use known commands)
- 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
- Lint every .tape file independently in CI before composing them
- Fix child-tape errors at the source; the parent only reports a count
- Keep child tapes small so errors stay easy to locate
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
- Expected path after Source
- Nested Source detected
- 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/504f53760d7d0100.
Report an issue: GitHub.