{"record":{"id":"504f53760d7d0100","repo":"charmbracelet/vhs","slug":"srcpath-has-count-errors","errorCode":null,"errorMessage":"${srcPath} has ${count} errors","messagePattern":"(.+?) has (.+?) errors","errorType":"validation","errorClass":"parser.Error","httpStatus":null,"severity":"error","filePath":"parser/parser.go","lineNumber":733,"sourceCode":"\t}\n\n\tsrcLexer := lexer.New(srcTape)\n\tsrcParser := New(srcLexer)\n\n\t// Check not nested source\n\tsrcCmds := srcParser.Parse()\n\tfor _, cmd := range srcCmds {\n\t\tif cmd.Type == token.SOURCE {\n\t\t\tp.errors = append(p.errors, NewError(p.peek, \"Nested Source detected\"))\n\t\t\tp.nextToken()\n\t\t\treturn []Command{cmd}\n\t\t}\n\t}\n\n\t// Check src errors\n\tsrcErrors := srcParser.Errors()\n\tif len(srcErrors) > 0 {\n\t\tp.errors = append(p.errors, NewError(p.peek, fmt.Sprintf(\"%s has %d errors\", srcPath, len(srcErrors))))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\tfiltered := make([]Command, 0)\n\tfor _, srcCmd := range srcCmds {\n\t\t// Output have to be avoid in order to not overwrite output of the original tape.\n\t\tif srcCmd.Type == token.SOURCE ||\n\t\t\tsrcCmd.Type == token.OUTPUT {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, srcCmd)\n\t}\n\n\tp.nextToken()\n\treturn filtered\n}\n","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/charmbracelet/vhs/blob/c073383b5de0b1f57bf514113029c306bc986539/parser/parser.go#L715-L751","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// child.tape before\nCopy setup.cfg\n// after\nCopy \"setup.cfg\"","handlingStrategy":"validation","validationCode":"// pre-validate the child tape before including it\nchild, _ := os.ReadFile(\"child.tape\")\nlp := lexer.New(string(child))\nsp := New(lp)\nsp.Parse()\nif n := len(sp.Errors()); n > 0 {\n    return fmt.Errorf(\"child.tape invalid: %d errors\", n)\n}","typeGuard":"func childTapeParses(path string) bool {\n    d, err := os.ReadFile(path)\n    if err != nil { return false }\n    sp := New(lexer.New(string(d)))\n    sp.Parse()\n    return len(sp.Errors()) == 0\n}","tryCatchPattern":"if err := parse(tape); err != nil && strings.Contains(err.Error(), \"has\") && strings.Contains(err.Error(), \"errors\") {\n    return fmt.Errorf(\"fix the included tape first: %w\", err)\n}","preventionTips":["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"],"tags":["parser","source","aggregate-errors"],"backgroundTag":"include-file-parse-errors","analyzedSha":"c073383b5de0b1f57bf514113029c306bc986539","analyzedAt":"2026-09-02T01:53:05.682Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}