Neu: Spare 20 % im ersten Monat — Code BRUCE20
Bereit zum Spielen?
Wähle dein Spiel, such dir einen Plan aus und spiele in unter 60 Sekunden los. Kein Lag, keine Ausreden.
Wuff! Los geht's!


BruceNode opened in early access on April 4. We opened to everyone on April 20. The 16 days in between weren't a stealth period — they were a public shakedown that produced 88 commits, three audit sweeps, and a list of fixes we would've been ashamed to ship without.
This is a founder-voice recap of what actually broke, what we fixed, and what we'd do differently if we had to do it again.
We ran three audit passes over the 16 days. Each one was a deliberate check against a different failure mode.
Audit 1 — Security surface (5-agent parallel). We split the code into five verticals (auth, billing, dashboard, admin, tools) and ran a dedicated review agent on each. The most important find: the disabled-user session gate had a 60-second window where a disabled account could still hit /dashboard because the middleware check was !session instead of !session.user?.id. The session callback blanks the id when disabled but returns the object anyway. Fix was four lines; the window is now zero.
Audit 2 — Billing + state drift. After a pre-launch database wipe, we noticed /admin/status was reporting 36.8GB of phantom RAM in use on a node that had zero servers on it. The counter columns hadn't been zeroed with the TRUNCATE. That's a minor display bug, but it's the canary for a bigger class — any stored summary that should equal a derived quantity drifts if one write path forgets to update both. We switched the admin dashboard to compute live from Server rows rather than trusting the counter, then reconciled the counter on-prod. Zero drift going forward.
Audit 3 — QA round. Fresh-eyes pass on the checkout flow. Caught the "select a region first" race (RegionPicker fetched async, fast clicks hit empty state), a missing payment-success confirmation, a chat widget that overlapped the Stripe form, and a promo-code field that didn't exist on the Stripe hosted checkout. Five issues, all shipped within a day.
Some of the best wins came from questions like "what's the worst thing a compromised session could do?"
authorize() throws TwoFactorRequiredError if the user has 2FA enabled and no OTP. OAuth gets the same treatment post-signin via a JWT-based challenge at /login/2fa.array_remove in raw Postgres with $hash = ANY(recoveryCodes) as the WHERE. Exactly-once semantics at the DB level, no read-then-write window.user.password is null, with identical timing for non-disclosure.Hourly billing ran a loop that read the wallet balance, capped the deduction, then executed a transaction. Two concurrent operations could both read a high balance, both deduct, and push the wallet negative — a narrow race, but real at scale.
We replaced it with an atomic compare-and-deduct: a single updateMany with walletBalance: { gte: total } in the WHERE clause. Postgres evaluates the gate inside the row lock at commit time. Either the claim succeeds or count === 0 and we suspend. No window for the race to slip through.
The whole site was loading a 300KB render-blocking CSS CDN from Font Awesome Pro to render five brand icons (Discord, Instagram, X, YouTube, Google). We ripped it out and replaced the icons with inline SVG in a single component. Every page got measurably faster.
This one sounds petty but matters: the blog grid was rendering the first "featured" post as 2-col-wide with a yellow "FEATURED" badge and a taller cover image. Every other card was half the size. The alignment mismatch jumped out visually. Stripped all featured-specific treatment. Every card is now the same shape. Grid reads as a grid again.
Don't set up two Stripe customer IDs (test + live) on the same User row. We did this to let test-mode dev still function with the same auth, but it's a smell. Either keep test/live auth flows fully separate or stop duplicating customer state. We'll revisit pre-1.1.
Wipe scripts should zero the counters they're about to invalidate. The /admin/status phantom-RAM bug wouldn't have existed if the wipe had reset Node counters alongside TRUNCATEing Server rows. Now the reconcile script is part of the wipe runbook.
Launch-day copy should be staged per channel before the dev freeze. We wrote the anchor blog post + Discord + Reddit + X copy in the same afternoon as the actual launch. Would have been calmer if that week-of content had been ready a week prior.
We're officially open at brucenode.com. BRUCE20 = 20% off the first month.
— Eshan & Tisha (and Bruce, the Boston Terrier with opinions)