# LESSONS.md — What We've Learned the Hard Way

*CC VPS reads this every session. Add to it when things break or patterns emerge.*
*Format: what happened → what to do instead*

---

## Code

- **Kimi agents must never touch code** — 5 build-breaking bugs in one session (Feb 7). All code goes through Claude Code.
- **Rivet itself must never write code either** — not just Kimi sub-agents. Feb 8: Rivet spawned Kimi agents for notifications UI, ratings UI, match algorithm, and conversation pagination. All duplicated work CC VPS had already done. Rivet's role is research/specs/plans/coordination only. Code = CC VPS only.
- **Stripe webhooks must throw on DB failure** — was silently returning success, Stripe never retried. Fixed Feb 8.
- **Always sanitize user input before DB insert** — message content wasn't sanitized, XSS risk. Fixed Feb 8.
- **Auth callbacks: validate redirect params** — open redirect via `next` param, could phish users. Fixed Feb 8.
- **Supabase returns empty on bad columns** — no error, just empty data. Always verify column names exist.
- **parseInt() needs base parameter** — use `parseInt(x, 10)` not `parseInt(x)`.
- **Check notification insert errors** — fire-and-forget inserts hide failures. Always check `{ error }`.
- **Type cast nested joins safely** — `match.jobs as { title: string }` crashes if null. Use optional chaining.

## Infrastructure

- **Next.js process goes stale silently** — returns 500 but process still shows as running. Need a health check cron that curls localhost and restarts if it fails. (Feb 8)
- **.git/objects owned by root breaks ccuser commits** — Rivet runs as root, creates root-owned objects. Fix: `chown -R ccuser:ccuser .git/` after Rivet touches repo.
- **Same issue with .next/ build cache** — root-owned .next/ blocks builds. Same fix. Health check cron also creates root-owned files when it restarts the app.
- **Never `systemctl restart clawdbot`** — use gateway restart tool only. Systemctl can break config state.
- **Config files must agree** — clawdbot.json, auth-profiles.json, models.json must stay in sync. Mismatch caused 12-hour crash (Feb 6).
- **Always validate config with `clawdbot doctor` before restart** — catches mismatches.
- **Tailscale exit node can break SSH** — if VPN exit node changes, SSH may fail. Check exit node config.
- **IPv6 issues with Supabase direct connections** — use Management API or pooler, not direct connection.

## Business

- **White Card is NOT RateRight's responsibility** — marketplace model, we connect, we don't employ. (Feb 7)
- **No payment protection insurance** — requires AFSL, too risky. Trust = reputation instead. (Feb 6)
- **No escrow** — doesn't fit the marketplace model. (Feb 6)
- **Matching is hybrid** — smart algorithm scores + manual wizard-of-oz overlay for first 3+ months. (Feb 6/8)
- **$50 flat fee, workers pay $0** — pricing model locked. The moat vs 20-40% agencies. (Feb 7)
- **Target GAPS not unemployed** — between-contract workers, rained-off days. (Feb 6)

## Process

- **Empty inbox ≠ no work** — heartbeat must scan TODO.md, not just TODO-INBOX.md. 2 hours wasted (Feb 7).
- **Sub-agents need explicit file paths** — Kimi agents web search instead of reading local files if you don't specify paths.
- **Don't push to mcloughlinmichaelr-debug** — that was Markus's workspace. Push to the right remote.
- **Draft external comms but never send without explicit "yes"** — Michael approves all outbound.

---

*When something breaks, add it here. Future-you will thank you.*
