Not every setback in building this thing has been a big strategic miss or a hard product decision. Some of it is dumber than that. On July 25th, I broke production over something that, on paper, sounds like it shouldn't be able to break anything at all: a line ending. An invisible character. Not a logic bug, not a bad API call, not a data problem. Whitespace.
I'm writing this one because it's the kind of mistake that doesn't feel like "real" engineering while it's happening. There's no stack trace pointing at your bad idea. There's just a diff that looks wrong for reasons you can't quite explain, and a decision you make in about four seconds that turns out to matter a lot more than it should.
How the setup was wrong before I ever touched it
The NarrateIQ repo didn't have a .gitattributes file. On a normal day that's invisible. It only matters because I'm on Windows, and Windows git installs commonly run with autocrlf turned on, which quietly converts line endings between LF and CRLF depending on which direction a file moves. Most of the time you never notice. The file content doesn't change, just the invisible character at the end of each line.
Without a .gitattributes file telling git how to treat those endings consistently, that conversion isn't pinned down. It can happen inconsistently across files, across checkouts, across whichever tool touched a file last. And git doesn't distinguish "this line's content changed" from "this line's ending changed." Both just show up as a modified line in a diff.
The phantom diff
That's the trap. A file can show up as modified, red and green lines and all, when literally nothing about its content is different. Just the ending character. I've since learned to call these phantom diffs, files that look edited but aren't, and on July 25th I ran into one and made the wrong call about it. When you're moving fast and a diff looks like a line-ending-only change, the instinct is to treat it as noise. Discard it, don't think about it, move on to the actual work. That instinct is usually right. It just isn't guaranteed to be right, and the day it isn't, you don't find out until something breaks that used to work.
What made it disorienting wasn't the fix. It was the category of mistake. I'm used to debugging logic. Something returns the wrong value, a condition is off by one, an API responds differently than expected, and you can reason your way to the cause. This wasn't that. The bug wasn't in anything I wrote. It was in how the tooling around what I wrote handled a character nobody looks at on purpose. You don't build instincts for that kind of failure the same way you build them for a bad function, because it doesn't announce itself as a bug. It announces itself as a diff you've seen a hundred times before, right up until the one time it isn't.
A diff that looks trivial isn't the same thing as a diff that is trivial.
That's the distinction I didn't respect closely enough. I discarded something that looked like line-ending noise without actually verifying there wasn't real content sitting inside it. There was. It broke production the same day.
What actually fixes this
The fix wasn't clever. I added a .gitattributes file to the repo to normalize line endings to LF across the project, committed as "chore: add .gitattributes to normalize line endings (LF)." Now every checkout and every commit treats endings the same way, regardless of which machine or which tool touched the file last. The category of bug that let a phantom diff exist in the first place is closed. That part was straightforward once I understood what had actually gone wrong.
The harder fix is the habit. Solo, moving fast, it's easy to build a reflex where "looks like a trivial diff" and "is a trivial diff" get treated as the same thing. They're not, and the gap between them is exactly where this bug lived.
What I'd tell another solo founder
If you're the only engineer on your own project, nobody is going to catch this for you. No second reviewer glancing at the diff, no CI check built specifically for it, unless you set one up yourself. So the discipline has to be yours. Before you discard anything that looks like a line-ending-only change, actually check. Stash it instead of dropping it. Diff it against what you expect. Confirm there's no real content hiding under what looks like whitespace noise, before you decide it's safe to throw away. It costs you thirty seconds. The alternative cost me a production break and an afternoon of figuring out why something that used to work suddenly didn't. Thirty seconds is cheap. I didn't spend it, and that's the whole lesson.
None of this makes it into a pitch deck. It's not a growth milestone or a feature launch. But it's part of the actual record of building this, and skipping it would make the story I'm telling in this series a little less honest than I want it to be. I'd rather write down the boring, avoidable mistake than pretend every week here has been a clean one.
Want a reporting pipeline that's already been through this so you don't have to be?
I run every account personally, so if something's off, I fix it directly.
Book a free audit call →