charmbracelet/crush · error

empty shebang

Error message

empty shebang

What it means

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.

Source

Thrown at internal/shell/dispatch.go:281

// parseShebang extracts the interpreter invocation from probe. It tolerates
// CRLF line endings and a single leading space between `#!` and the path.
// env special-cases: `/usr/bin/env NAME [args...]` unwraps to NAME with
// kernel single-arg semantics; `-S` enables tokenized argument splitting.
func parseShebang(probe []byte) (*shebang, error) {
	if !hasShebang(probe) {
		return nil, errors.New("not a shebang")
	}
	line := probe[2:]
	// Take up to the first newline.
	if idx := bytes.IndexByte(line, '\n'); idx >= 0 {
		line = line[:idx]
	}
	// Strip trailing CR (CRLF-authored scripts).
	line = bytes.TrimRight(line, "\r")
	// Strip leading whitespace ("#! /usr/bin/env bash" is legal).
	line = bytes.TrimLeft(line, " \t")
	if len(line) == 0 {
		return nil, errors.New("empty shebang")
	}

	var pathStr, rest string
	if idx := bytes.IndexAny(line, " \t"); idx >= 0 {
		pathStr = string(line[:idx])
		rest = strings.TrimLeft(string(line[idx+1:]), " \t")
	} else {
		pathStr = string(line)
	}

	if isEnvShebang(pathStr) {
		return parseEnvShebang(rest)
	}

	// Literal-path shebang: kernel semantics pass the remainder as a
	// single argv[1], not tokenized.
	sb := &shebang{interpreter: pathStr}
	if rest != "" {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Fix the script's first line to a valid shebang like '#!/bin/bash' or '#!/usr/bin/env bash'.
  2. Remove the empty shebang line entirely if the script doesn't need one.
  3. Check for CRLF authoring issues and correct the shebang line ending.
  4. If the file is generated, fix the generator template to emit a complete shebang.

Example fix

#!/bin/bash
// before
#!
#!/bin/sh -e
// after
#!/bin/sh -e
Defensive patterns

Strategy: validation

Validate before calling

first, _ := bufio.NewReader(f).ReadString('\n')
line := strings.TrimSpace(strings.TrimRight(first, "\r"))
if !strings.HasPrefix(line, "#!") || len(strings.TrimSpace(line[2:])) == 0 {
    return errors.New("script has empty/invalid shebang")
}

Type guard

func hasValidShebang(line []byte) bool {
    line = bytes.TrimLeft(bytes.TrimRight(line, "\r"), " \t")
    return len(line) > 2
}

Try / catch

if err := dispatchShebang(script); err != nil {
    if strings.Contains(err.Error(), "empty shebang") {
        return fmt.Errorf("script %s: fix line 1 with a real interpreter", script)
    }
    return err
}

Prevention

When it happens

Trigger: Dispatching a script whose first line is '#!', '#! ' (only whitespace after #!), or is otherwise blank after trimming.

Common situations: A hand-written or generated script with a malformed shebang line, or a file accidentally truncated so only '#!' remains on line 1.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/ecc3d069da9c44af. Report an issue: GitHub.