Debugging
Lessons learned from bugs, investigations, and things that went wrong.
General Principles
- Read the error message. The whole error message.
- Reproduce before fixing. If you can't trigger it, you don't understand it.
- Don't brute force. If something is blocked, step back and think about why.
- When a test fails, read the test first — the test might be wrong.
Known Gotchas
YAML Auto-Typing
YAML will silently convert strings like yes, no, on, off to booleans. This is why frontmatter is parsed as map[string]string — we control the type conversion ourselves.
Symlink Resolution in Tests
When testing the versioned store, be careful with os.ReadFile vs following symlinks. The store creates doc.md -> versions/doc.md.v3 — some operations need the symlink target, others need the symlink itself.
Path Traversal
filepath.Clean handles most cases but you still need an explicit .. check after cleaning. A path like /../../../etc/passwd cleans to /etc/passwd which looks valid but is outside the content root. The check is: clean the path, then verify it doesn't escape the root.
QUIC Stream Lifecycle
Streams must be closed properly. If a handler panics or returns without closing the stream, the client hangs waiting for data. Always defer stream close.
Debugging Approach for This Project
- Write a failing test that reproduces the issue
- Fix the code
- Verify the test passes
- Check if the fix breaks anything else (
make test) - Run
bash pre-commit.sh
The test-first approach isn't dogma here — it's practical. A test that reproduces the bug proves you understand the bug, and it prevents regression.