Dogfooding is a brilliant bug-finder and a terrible product manager
Every tool here is used to run the place, including the one reviewing this draft. That catches a class of defect nothing else does. It also produces a convincing-looking roadmap built entirely from the preferences of the least representative user available.
This post was reviewed in one of the products it's about.
The markdown is canonical in the site's own repository. Each review round gets published into Kythene - our knowledge and review tool - as a collection with review switched on, comments come back against individual blocks, and the markdown gets a new revision. So the piece and the tool have spent a week arguing with each other.
Which is how I found out that review blocks anchor on headings, not on
arbitrary quoted text. Referring to a block by a phrase from the middle of a
paragraph mostly doesn't resolve; referring to it by its ## heading always
does. Nobody would ever have filed that. It's not a crash, no test fails, and the
API returns a perfectly good response - the mental model the feature invites and
the mental model it implements just aren't the same one, and you only find that
out by trying to use it for something real.
That gap is what dogfooding is genuinely good for. It's also, as far as I can tell, the only thing it's genuinely good for, and the second half of this is about why.
What actually gets eaten
Seven things, all load-bearing rather than ceremonial:
- Kythene - where decisions, findings and measured facts live, and now the review surface for this writing
- Notom - notes and to-dos, driven over its own MCP interface
- mktg - surfaces online conversations worth answering
- outreach - the CRM behind a live sales campaign
- Margot - the marketing site for Margot is itself a Margot site, authored
over Margot's own MCP connector. It returns
<meta name="generator" content="Margot">, which I checked on 17 August 2026 before writing this sentence, because it's the strongest claim in the list - central - the estate inventory
- the
tam,todandkythecommand-line tools, daily
Four defects, and what would have caught them
Same pattern in each: a test asserting the code does what it says passes, because the code does do what it says.
A render cache keyed on the filename
Editing a draft and reloading served the previous version back. Every time. One line:
if rf, ok := c.rendercache[fname]; ok {
return rf, nil
}
The first render of a document was the only one the process ever produced. Fixed on 16 August 2026 by keying on modification time as well:
func (c *cs) cacheKey(fname string) string {
if stat, err := fs.Stat(c.config.Fs, fname); err == nil {
return fname + "\x00" + strconv.FormatInt(stat.ModTime().UnixNano(), 10)
}
return fname
}
What would have caught it: nothing we had, and nothing reasonable. In production the content tree is an embedded filesystem, which can't change under a running binary, so caching on the name alone isn't merely acceptable there - it's right, and faster. The bug only exists in development, where the tree is read from disk and changing it is the entire point. A unit test written against the production configuration passes, and should.
You find that one by writing something and watching your own words fail to turn up.
Search returning the wrong thing, confidently
Dogfooding Kythene's recall against a real corpus on 5 August 2026, I asked it how integrations should work. Back came a data-protection pack. It missed the note whose title contained the word "integrations", and whose body was the decision I was after.
Hybrid search mis-ranked when the query phrasing diverged from the title. No error anywhere - the API returns 200 with plausible, related, wrong results, and if you don't already know the right answer exists you conclude the system hasn't got it. (That one became the blocking ticket for a migration off the old store, which is the sort of consequence a fixture-based test suite will never generate for you.)
What would have caught it: a test with a corpus big enough to contain near misses. Ours had fixtures. Fixtures get chosen to be distinguishable, which is exactly the property that makes them useless for testing ranking.
A field that looked empty and wasn't
Reading a page through the site-authoring API omitted the values of rich-text nodes. So a populated field came back looking empty, and the natural next move - write the content you think is missing - destroys what was there, silently.
What would have caught it: a round-trip test. Read a page, write it back unchanged, assert equality. We had tests for read and tests for write, and nothing asserting that the composition of the two is the identity function. Any read/modify/write API wants that test, and hardly any of them have it.
A panic at boot, on a path nobody called
Registering a non-CRUD verb on the API surface without mapping it to an action took the whole application down at startup:
libapi: ToolSet("sites") has no action for verb "publish" (request "PublishPage");
add it with ToolSet.Verb
Only when the API was actually mounted, though, which not every deployment does. So it built clean, started clean everywhere it was being run, and waited.
What would have caught it: the staging deploy, which is where it was caught, and roughly the whole reason that environment earns its keep. Building and starting are different claims and CI only makes the first one.
The fix wasn't to stop panicking, incidentally. The panic is correct - an unmapped verb defaulting to some permission is how you ship an authorisation hole - so it stayed, and the test that now guards it says so:
// An unmapped verb must fail closed (panic at build time) rather than default
Where this stops being useful
Most posts on this subject finish at the section above, on a high. The rest is why I don't trust it for anything except defects.
My own usage is a sample of one, and it's the least representative sample available. I know the internals. When something's awkward I route around it without registering that I did - I know which button is the real one, which order the steps go in, which field is safe to leave blank. A new user has none of that and hits the awkwardness head-on. So the friction I feel is the friction that survives expertise, which is a strange and narrow subset of the friction that exists.
I never hit onboarding, because I've never onboarded. Every account I use was created before the product had a sign-up flow worth the name. The whole first-run experience - the part that decides whether anyone becomes a user at all - is invisible to me by construction. I can go and use it deliberately, and I have, but doing that while knowing what's on the other side is a different experience from arriving cold, and only one of them is the one that matters.
And it feels like customer research. That's the part that does the damage. Dogfooding produces exactly the same artefact user research produces - a list of things to build, with reasons attached - and once they're both in a backlog you can't tell them apart. So the founder's own preferences get promoted into a roadmap wearing the clothes of evidence, and every item on it is individually defensible.
The tell is in what the list contains. Mine skews hard towards command-line tools and API surfaces, because that's how I use these things. It says almost nothing about first-run, empty states, the pricing page, or what happens when somebody who hasn't read the source clicks the wrong thing. Those are the parts that decide whether a product works for anyone else, and my usage generates no signal about them at all.
What I do with it now
Two rules, and the second took a lot longer to accept than the first.
Use it for defect discovery, deliberately. Write the friction down as it happens, because routing around it is automatic and the note isn't. Most of what it catches would never have been filed by anybody - it isn't the kind of thing that generates a bug report, it's the kind of thing that generates a shrug.
Don't use it to decide what to build. Those decisions need somebody who isn't me, and the fact that dogfooding produces a convincing-looking roadmap is what makes it dangerous rather than merely limited. A weak signal you know is weak is fine. A weak signal that looks strong is how you spend six months polishing the part of the product only you use.
What the sample-of-one problem turns into
At one person, the obvious flaw is the sample size. In a large organisation that is fixed - internal users are numerous and varied, and the statistical objection goes away.
It is replaced by something harder to see: internal users stop being typical. Not because they are privileged, though they are, but because they learn. They know which screen is slow and open it in a second tab. They know the import fails on a file with a byte-order mark and strip it without thinking. They route around every rough edge unconsciously, and the routing-around is invisible even to them.
So the broken paths stay broken, and they stay broken no matter how many internal users you add, because every one of them acquires the same workarounds. The friction you are measuring is the friction of people who have already adapted to it, which is the one population whose experience tells you nothing about a new customer's.
Formalising dogfooding is mostly about defeating that. Record friction at the moment it happens rather than at a retrospective, because by the retrospective it has been absorbed. Rotate who uses what, so somebody is always new. And treat a long-tenured internal user's "it's fine" as the least reliable signal in the building.
The thing I'd tell you
Dogfooding is instrumentation. It tells you when your own machine is broken, in places no test looks, and it tells you nothing reliable about whether anybody else wants the machine.
Both halves have been true here for months and I kept only the flattering one for most of them.
So the question worth asking isn't whether you dogfood - if you built it, you obviously do - but what your usage systematically can't see. Mine is everything before the command line. I'd be interested to know what yours is.