{"record":{"id":"3496d2a1f206ce8c","repo":"charmbracelet/vhs","slug":"expected-path-after-source","errorCode":null,"errorMessage":"Expected path after Source","messagePattern":"Expected path after Source","errorType":"validation","errorClass":"parser.Error","httpStatus":null,"severity":"error","filePath":"parser/parser.go","lineNumber":676,"sourceCode":"\tif p.peek.Type != token.STRING {\n\t\tp.errors = append(p.errors, NewError(p.peek, p.cur.Literal+\" expects string\"))\n\t}\n\n\tcmd.Args = p.peek.Literal\n\tp.nextToken()\n\n\treturn cmd\n}\n\n// parseSource parses source command.\n// Source command takes a tape path to include in current tape.\n//\n//\tSource <path>\nfunc (p *Parser) parseSource() []Command {\n\tcmd := Command{Type: token.SOURCE}\n\n\tif p.peek.Type != token.STRING {\n\t\tp.errors = append(p.errors, NewError(p.cur, \"Expected path after Source\"))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\tsrcPath := p.peek.Literal\n\n\t// Check if path has .tape extension\n\text := filepath.Ext(srcPath)\n\tif ext != \".tape\" {\n\t\tp.errors = append(p.errors, NewError(p.peek, \"Expected file with .tape extension\"))\n\t\tp.nextToken()\n\t\treturn []Command{cmd}\n\t}\n\n\t// Check if tape exist\n\tif _, err := os.Stat(srcPath); os.IsNotExist(err) { //nolint:gosec\n\t\tnotFoundErr := fmt.Sprintf(\"File %s not found\", srcPath)\n\t\tp.errors = append(p.errors, NewError(p.peek, notFoundErr))","sourceCodeStart":658,"sourceCodeEnd":694,"githubUrl":"https://github.com/charmbracelet/vhs/blob/c073383b5de0b1f57bf514113029c306bc986539/parser/parser.go#L658-L694","documentation":"parseSource requires a STRING path token after the Source directive. If absent, it records \"Expected path after Source\" and returns immediately. Source includes another .tape file, so a path argument is mandatory.","triggerScenarios":"A .tape file containing bare `Source` with no argument, or an unquoted token (e.g. `Source other.tape` without quotes) so the path token is not of type STRING when parseSource runs.","commonSituations":"Copy-pasting `Source` examples without quotes; forgetting the argument when commenting out the path; concatenating lines so the path lands on the wrong line.","solutions":["Add a quoted path: `Source \"other.tape\"`","Remove the Source line if it is unused","Confirm the path token is double-quoted so the lexer emits a STRING"],"exampleFix":"// before\nSource other.tape\n// after\nSource \"other.tape\"","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(line) == \"Source\" {\n    return fmt.Errorf(\"Source requires a quoted .tape path\")\n}\nif strings.HasPrefix(line, \"Source \") && !strings.Contains(line, `\"`) {\n    return fmt.Errorf(\"Source path must be double-quoted\")\n}","typeGuard":"func sourceHasPath(t token.Token) bool { return t.Type == token.STRING && t.Literal != \"\" }","tryCatchPattern":"if err := parse(tape); err != nil && strings.Contains(err.Error(), \"Expected path after Source\") {\n    return fmt.Errorf(\"bad Source directive in %s: %w\", tape, err)\n}","preventionTips":["Always write Source \"file.tape\" with quotes","Keep one directive per line","Add a pre-run syntax check of all tape files"],"tags":["parser","syntax","source"],"backgroundTag":"missing-required-argument","analyzedSha":"c073383b5de0b1f57bf514113029c306bc986539","analyzedAt":"2026-09-02T01:53:05.682Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}