Whitespace bugs are uniquely cruel. The code looks correct. The diff shows nothing suspicious. The error message points at the wrong line, or gives no line at all. And once you find the fix — deleting an invisible character or changing a line ending — the patch looks like an empty commit.

This post catalogues seven whitespace bugs that appear repeatedly in real deployments, explains exactly what triggers each one, and shows how to reproduce and fix it. All seven have caused actual production incidents. All seven are invisible to a basic code review.

The Seven Bugs

1. Windows CRLF Line Endings in a Bash Script

When a bash script is written or edited on Windows and then deployed to a Linux server, each line may end with \r\n (CRLF) instead of the expected \n (LF). The \r character is not stripped by bash — it becomes part of the command, causing the error:

/bin/bash^M: bad interpreter: No such file or directory

Or silently corrupts string comparisons:

# On Windows-edited file:
NAME="deploy"$'\r'

if [ "$NAME" = "deploy" ]; then
    echo "match"   # never printed — $'\r' breaks the comparison
fi

Detection: cat -A script.sh | head -5 — CRLF lines end with ^M$ instead of $.

Fix: sed -i 's/\r//' script.sh or configure git with git config core.autocrlf input to auto-convert on checkout.

2. Byte Order Mark (BOM) at the Start of a Shell Script

A UTF-8 BOM (U+FEFF, the three bytes 0xEF 0xBB 0xBF) at the very start of a file causes bash to fail with a confusing error:

: No such file or directoryy.sh: line 1:

The shebang #!/bin/bash becomes \xef\xbb\xbf#!/bin/bash, which bash cannot recognize. The BOM is added by some Windows text editors (Notepad, older Excel exports, some IDE defaults) when saving UTF-8 files.

Detection:

hexdump -C script.sh | head -1
# With BOM:    00000000  ef bb bf 23 21  |...#!|
# Without BOM: 00000000  23 21            |#!|

Fix: Remove BOM with sed -i '1s/^\xef\xbb\xbf//' script.sh. Prevention: configure editors to save UTF-8 without BOM. A Smart Text Cleaner that strips invisible Unicode characters including the BOM catches this before the file goes into source control.

3. Trailing Whitespace in a YAML Multi-line String

YAML block scalars (using | or >) strip trailing whitespace from lines by default. But trailing whitespace after the block indicator itself — on the same line as | — can cause parsers to reject the file or misinterpret the block style.

# This is valid YAML:
message: |
  Hello world

# This may cause parser errors depending on implementation:
message: |
  Hello world

More subtly, trailing spaces inside a literal block string (after |) are preserved verbatim. A config value that looks like "production" but is actually "production " (with trailing space) will fail string equality checks without any error message — the service just behaves incorrectly.

4. Tabs vs Spaces in Python Source Files

Python 3 raises a TabError when a file mixes tab and space indentation within the same indented block. Python 2 silently converted tabs to 8 spaces, which caused incorrect parsing of visually correct-looking code. Python 3 makes the error explicit:

TabError: inconsistent use of tabs and spaces in indentation

This most commonly appears when copying code from a source that uses tabs into a project that uses spaces, or when an editor with inconsistent settings saves part of a file with tabs and part with spaces.

Detection: python3 -tt script.py treats tabs mixed with spaces as an error even in Python 2 compatibility mode. grep -Pn "\t" *.py finds all tab-indented lines.

5. Non-Breaking Space (U+00A0) in a Configuration Value

Non-breaking spaces look identical to regular spaces in most text editors and terminal output. They are introduced when copying text from web pages, word processors, or PDF exports. In a configuration file they are silent killers.

# In .env file — looks correct, but POSTGRES_HOST contains U+00A0
POSTGRES_HOST=db.internal

The driver receives db.internal  as the hostname. DNS resolution fails. The error: "could not translate host name to address." The developer rereads the config file three times and sees nothing wrong.

Detection in shell:

cat -v config.env | grep $'\xc2\xa0'
# U+00A0 encodes as 0xC2 0xA0 in UTF-8

Pasting config values through the text cleaner before committing them to source control replaces U+00A0 with a regular space automatically.

6. Trailing Whitespace in a .env File Value

Most .env parsers — including dotenv for Node.js, python-dotenv, and the shell's export command — include trailing whitespace as part of the value. A database URL written as:

DATABASE_URL=postgres://user:pass@localhost/db   

(with three trailing spaces) will cause a connection error. The logged URL looks correct because terminal output trims trailing whitespace in display. The actual string sent to the driver includes the spaces.

7. Mixed Indentation in a Makefile Recipe

Makefile recipes must be indented with exactly one hard tab character. Spaces look identical in most editors but cause Make to emit the infamous error:

Makefile:12: *** missing separator.  Stop.

This error is particularly confusing because "missing separator" refers to the tab character, not any visible separator. The line looks correctly indented.

# Correct (tab-indented recipe):
build:
	go build ./...

# Incorrect (space-indented — breaks Make):
build:
    go build ./...

Detection: cat -A Makefile | grep "^ " — space-indented recipe lines will appear as go build instead of ^Igo build.

How to Detect These Issues Before They Deploy

The most reliable prevention layer is a pre-commit hook combined with editor configuration. Effective approaches:

Git attributes + editorconfig
A .editorconfig file at the repo root specifies indentation style, end-of-line character, and trim_trailing_whitespace. Most editors respect it automatically.

# .editorconfig
root = true

[*]
end_of_line = lf
trim_trailing_whitespace = true
charset = utf-8
insert_final_newline = true

[Makefile]
indent_style = tab

[*.py]
indent_style = space
indent_size = 4

hexdump for BOM detection

# Check first bytes of any file
hexdump -C "$FILE" | head -1 | grep -q "ef bb bf" && echo "BOM detected"

CI/CD whitespace gate
Add a step to your CI pipeline:

# Fail if any tracked file has CRLF line endings
git diff --check HEAD~1 HEAD

Quick Fixes With a Text Cleaner

When you receive config values, environment variables, or scripts from external sources — emails, chat messages, web forms — the safest practice is to paste them through a tool that strips invisible characters before use. The Smart Text Cleaner handles BOM removal, NBSP-to-space conversion, and trailing whitespace stripping in one step. It also shows you exactly what was removed, so you can diagnose the source of the problem rather than just silently fixing it.

Invisible-character bugs are documented more thoroughly in hidden characters that break copy-paste — the overlap between "characters that break strings" and "characters that break deploys" is significant.

For one-off cleanup tasks, the quickest path is to paste the file through the text cleaner before committing it to source control. The tool highlights every removed character individually, which makes it easy to understand where the invisible content came from and whether the cleaned output is what you intended.