{"record":{"id":"4be1b97096ec46f3","repo":"caddyserver/caddy","slug":"missing-opening-heredoc-marker-on-line-d-must-c","errorCode":null,"errorMessage":"missing opening heredoc marker on line #%d; must contain only alphanumeric characters, dashes and underscores; got empty string","messagePattern":"missing opening heredoc marker on line #(.+?); must contain only alphanumeric characters, dashes and underscores; got empty string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/caddyfile/lexer.go","lineNumber":158,"sourceCode":"\t\tif (!quoted && !btQuoted) && (!inHeredoc && !heredocEscaped) &&\n\t\t\tlen(val) > 1 && string(val[:2]) == \"<<\" {\n\t\t\t// a space means it's just a regular token and not a heredoc\n\t\t\tif ch == ' ' {\n\t\t\t\treturn makeToken(0), nil\n\t\t\t}\n\n\t\t\t// skip CR, we only care about LF\n\t\t\tif ch == '\\r' {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// after hitting a newline, we know that the heredoc marker\n\t\t\t// is the characters after the two << and the newline.\n\t\t\t// we reset the val because the heredoc is syntax we don't\n\t\t\t// want to keep.\n\t\t\tif ch == '\\n' {\n\t\t\t\tif len(val) == 2 {\n\t\t\t\t\treturn false, fmt.Errorf(\"missing opening heredoc marker on line #%d; must contain only alphanumeric characters, dashes and underscores; got empty string\", l.line)\n\t\t\t\t}\n\n\t\t\t\t// check if there's too many <\n\t\t\t\tif string(val[:3]) == \"<<<\" {\n\t\t\t\t\treturn false, fmt.Errorf(\"too many '<' for heredoc on line #%d; only use two, for example <<END\", l.line)\n\t\t\t\t}\n\n\t\t\t\theredocMarker = string(val[2:])\n\t\t\t\tif !heredocMarkerRegexp.Match([]byte(heredocMarker)) {\n\t\t\t\t\treturn false, fmt.Errorf(\"heredoc marker on line #%d must contain only alphanumeric characters, dashes and underscores; got '%s'\", l.line, heredocMarker)\n\t\t\t\t}\n\n\t\t\t\tinHeredoc = true\n\t\t\t\tl.skippedLines++\n\t\t\t\tval = nil\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tval = append(val, ch)","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/caddyfile/lexer.go#L140-L176","documentation":"The lexer saw '<<' followed immediately by a newline: the heredoc opening marker is empty. The marker name (the characters between << and end of line) is required and must be alphanumeric/dash/underscore.","triggerScenarios":"A line ends with just '<<' — e.g. 'respond <<' with the marker forgotten, or a template placeholder accidentally left in a Caddyfile.","commonSituations":"Forgetting to name the marker after <<, or envsubst/template rendering that consumed the marker text, leaving bare '<<' at end of line.","solutions":["Add a marker name right after << on the same line (e.g. <<EOF)","Close the heredoc with a matching line containing only the marker","Check templating that generates the Caddyfile for empty substitutions"],"exampleFix":"# before\nrespond <<\n  hi\nEOF\n# after\nrespond <<EOF\n  hi\nEOF","handlingStrategy":"validation","validationCode":"if strings.HasSuffix(line, \"<<\") {\n    return fmt.Errorf(\"empty heredoc marker on %q\", line)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write the marker immediately after << on the same line","Lint generated Caddyfiles for dangling '<<'","Test templates that render Caddyfiles with example data"],"tags":["caddyfile","heredoc","lexer"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}