{"record":{"id":"ecc3d069da9c44af","repo":"charmbracelet/crush","slug":"empty-shebang","errorCode":null,"errorMessage":"empty shebang","messagePattern":"empty shebang","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/shell/dispatch.go","lineNumber":281,"sourceCode":"// parseShebang extracts the interpreter invocation from probe. It tolerates\n// CRLF line endings and a single leading space between `#!` and the path.\n// env special-cases: `/usr/bin/env NAME [args...]` unwraps to NAME with\n// kernel single-arg semantics; `-S` enables tokenized argument splitting.\nfunc parseShebang(probe []byte) (*shebang, error) {\n\tif !hasShebang(probe) {\n\t\treturn nil, errors.New(\"not a shebang\")\n\t}\n\tline := probe[2:]\n\t// Take up to the first newline.\n\tif idx := bytes.IndexByte(line, '\\n'); idx >= 0 {\n\t\tline = line[:idx]\n\t}\n\t// Strip trailing CR (CRLF-authored scripts).\n\tline = bytes.TrimRight(line, \"\\r\")\n\t// Strip leading whitespace (\"#! /usr/bin/env bash\" is legal).\n\tline = bytes.TrimLeft(line, \" \\t\")\n\tif len(line) == 0 {\n\t\treturn nil, errors.New(\"empty shebang\")\n\t}\n\n\tvar pathStr, rest string\n\tif idx := bytes.IndexAny(line, \" \\t\"); idx >= 0 {\n\t\tpathStr = string(line[:idx])\n\t\trest = strings.TrimLeft(string(line[idx+1:]), \" \\t\")\n\t} else {\n\t\tpathStr = string(line)\n\t}\n\n\tif isEnvShebang(pathStr) {\n\t\treturn parseEnvShebang(rest)\n\t}\n\n\t// Literal-path shebang: kernel semantics pass the remainder as a\n\t// single argv[1], not tokenized.\n\tsb := &shebang{interpreter: pathStr}\n\tif rest != \"\" {","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/shell/dispatch.go#L263-L299","documentation":"parseShebang could not extract an interpreter path from the first line of a script: after stripping CR and leading whitespace, the shebang line was effectively empty. This guards against files whose first line is just '#!' or whitespace.","triggerScenarios":"Dispatching a script whose first line is '#!', '#! ' (only whitespace after #!), or is otherwise blank after trimming.","commonSituations":"A hand-written or generated script with a malformed shebang line, or a file accidentally truncated so only '#!' remains on line 1.","solutions":["Fix the script's first line to a valid shebang like '#!/bin/bash' or '#!/usr/bin/env bash'.","Remove the empty shebang line entirely if the script doesn't need one.","Check for CRLF authoring issues and correct the shebang line ending.","If the file is generated, fix the generator template to emit a complete shebang."],"exampleFix":"#!/bin/bash\n// before\n#!\n#!/bin/sh -e\n// after\n#!/bin/sh -e","handlingStrategy":"validation","validationCode":"first, _ := bufio.NewReader(f).ReadString('\\n')\nline := strings.TrimSpace(strings.TrimRight(first, \"\\r\"))\nif !strings.HasPrefix(line, \"#!\") || len(strings.TrimSpace(line[2:])) == 0 {\n    return errors.New(\"script has empty/invalid shebang\")\n}","typeGuard":"func hasValidShebang(line []byte) bool {\n    line = bytes.TrimLeft(bytes.TrimRight(line, \"\\r\"), \" \\t\")\n    return len(line) > 2\n}","tryCatchPattern":"if err := dispatchShebang(script); err != nil {\n    if strings.Contains(err.Error(), \"empty shebang\") {\n        return fmt.Errorf(\"script %s: fix line 1 with a real interpreter\", script)\n    }\n    return err\n}","preventionTips":["Always start scripts with a full '#!/path/to/interp' line.","Lint generated scripts for bare '#!' lines.","Watch for CRLF-authored scripts mangling shebangs."],"tags":["shell","shebang","malformed-input"],"backgroundTag":"invalid-shebang","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}