# 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 1. Write a failing test that reproduces the issue 2. Fix the code 3. Verify the test passes 4. Check if the fix breaks anything else (`make test`) 5. 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. ### QUIC UDP Buffer Size Warning When running demarkus in CI or containerized environments, quic-go logs this warning: ``` failed to sufficiently increase receive buffer size (was: 1024 kiB, wanted: 7168 kiB, got: 2048 kiB) ``` **Not a failure.** quic-go tries to increase the UDP receive buffer to 7168 kiB for optimal performance. In environments with restricted sysctl limits, it can only get partway there. The connection still works — everything publishes fine. **Fix (if needed):** Increase the system limit with `sysctl -w net.core.rmem_max=7168000`. Not necessary for correctness, only for optimal throughput under heavy load. **First seen:** CI publishing content to demarkus-hub (`hub.demarkus.io`). ### OnNode Callback Concurrency `graph.Crawl` invokes `OnNode` from multiple worker goroutines. Any mutable state captured by the callback must use `sync/atomic` or a mutex. The `CrawlAndPersist` MaxNodes counter uses `atomic.Int32` for this reason. Always run `go test -race` on packages that use `OnNode`. ### Glamour WithAutoStyle() Blocks TUI Rendering **Symptom:** TUI shows "Loading..." for up to 60 seconds after fetch completes. Server logs confirm the FETCH is handled immediately. CLI works fine. A mouse scroll "unsticks" the display. **Root cause:** `glamour.WithAutoStyle()` calls `termenv.HasDarkBackground()`, which sends an OSC 11 escape sequence to the terminal and reads the response from stdin. But Bubbletea is also reading stdin for input events. This causes two problems: 1. **Stdin contention** — Bubbletea's input goroutine may consume the OSC 11 response, so termenv never sees it and blocks for the full 5-second timeout. 2. **Render corruption** — The stdin race breaks Bubbletea's render cycle, leaving the view stuck on "Loading..." even though the model state has been updated. A mouse event forces a fresh render, which is why scrolling "fixes" it. The TUI recreated the glamour renderer on every window resize (to update word wrap width), re-triggering the query each time. Multiple resize events at startup compounded into 30-60 seconds of blocking. **Fix:** Detect dark/light background once in `main()` before `tea.NewProgram().Run()` takes over stdin. Pass the resolved style name (`"dark"` or `"light"`) into the model. Use `glamour.WithStandardStyle(styleName)` instead of `glamour.WithAutoStyle()` — this hits the `DefaultStyles` map lookup directly, bypassing the terminal query entirely. Also added a `-style` flag so users can skip detection. **Guard:** Both stdin and stdout must be terminals before attempting detection. If either is piped, fall back to dark. **Key insight:** Never do terminal escape sequence queries inside a Bubbletea event loop. Bubbletea owns stdin once it starts. Any library that reads stdin for terminal responses will race with Bubbletea's input goroutine.