19 Bugs in 2 Hours: What Happens When You Actually Try to Use Your AI-Built Enterprise App

By Pushpak Pujari · February 14, 2026 · 11 min read

I declared victory twice. Both times I was wrong.

Let me tell you about the worst two hours of my recent engineering life. We'd just finished an autonomous build loop — 85 engineering tasks, executed by Claude Code over ~16 hours, building a full enterprise Contract Lifecycle Management (CLM) application. NestJS backend with Prisma, Next.js frontend, multi-tenancy, RBAC, the works. The build loop reported success. TypeScript compiled. Next.js built. Tests passed. I messaged the team: "CLM MVP is done." Then I opened a browser.

2:15 PM — The App Doesn't Exist

I hit localhost:3000. Blank page. No login screen. No error message. Just... nothing. Bug #1: The build loop had created 80+ feature pages (contracts, templates, clauses, approvals) but never created the scaffolding. No root layout.tsx. No auth layout. No dashboard layout. No login page. Next.js App Router requires layout.tsx at each route group level. This should have been Task #1, not something buried in phase 3. The autonomous loop built an entire house — bedrooms, kitchen, bathrooms — but forgot the front door. I created four layout files and a login page from scratch. Thirty minutes in, zero features tested.

2:20 PM — "Tenant ID Required"

Login page rendered. Entered credentials. Hit Sign In. Bug #2: "Tenant ID is required." Every API call needed an x-tenant-id header. The build loop had implemented multi-tenancy perfectly — for a production environment with a tenant provisioning flow. For a dev environment where I just want to log in? Completely unusable. I added a DEFAULT_TENANT_ID env var and middleware bypass. Multi-tenancy middleware must have a dev-mode escape hatch. The AI built for the spec, not for the human trying to use it.

2:30 PM — Login Works, Everything Else Crashes

JWT came back. Dashboard loaded. Then immediately crashed. Bug #3: Three compounding auth failures. The JWT strategy didn't load user permissions. The permissions query used wrong Prisma relation names. The JWT user object was missing the id field entirely. DataAccessService expected req.user.id and got undefined. Auth is a chain — JWT creation → validation → user hydration → permission loading → route guards. The build loop built each link in isolation. Nobody tested the chain end-to-end. Every individual piece worked. Together, they crashed.

2:45 PM — Prisma's Silent Massacre

Bug #4: Various API endpoints returned 500 errors on any query involving joins. The multi-tenancy middleware auto-injected WHERE tenantId = ? into ALL Prisma queries. But models like ContractRelationship, ContractShare, DocumentContent, and SignatureRequest don't have a tenantId field. I had to add 15 models to the exclusion list. The build loop created new models without knowing to update the middleware's exclusion list. Auto-injection middleware is a landmine if your exclusions aren't maintained.

2:50 PM — The ORM vs SQL Identity Crisis

Bug #5: Search endpoint — 500 error. The raw SQL queries used Prisma model names ("Contract", "Template") instead of PostgreSQL table names ("contracts", "templates"). $queryRaw bypasses the ORM. Prisma model names don't apply. Bug #6: Same endpoint, second error. LIMIT $N with a string parameter. PostgreSQL wanted an integer. Inline LIMIT ${Number(limit)} fixed it. Two bugs in the same file. One conceptual (ORM vs SQL naming), one trivial (type casting). Both invisible until runtime.

2:55 PM — The Phantom Relation

Bug #7: Audit logs endpoint — 500 error. The service included { user: { select: { id, name, email } } } but the AuditLog model has no user relation in the Prisma schema. Here's the insidious part: Prisma compiles fine with non-existent includes. TypeScript didn't catch it. The build didn't catch it. Only a real API call at runtime surfaced the error.

3:00 PM — The Permissions Gap

Bug #8: Reports endpoint — 403 Forbidden. Even for Admin users. The seed script simply didn't include report:read and report:export permissions. The build loop created the reports feature, created the RBAC guards, but never seeded the permissions those guards check.

3:13 PM — The Method That Never Was

Bug #9: TypeScript caught this one — ObligationService.listAll() was referenced but never implemented. A method signature existed, calling code existed, but the body? Missing. The build loop wrote the consumer before the provider.

3:30 PM — Three Pages That Don't Exist

Bug #10: After fixing all the API issues, I finally opened a browser and clicked through the sidebar. Contracts? Blank. Search? Error. Admin? Nothing. The build loop created API endpoints and some frontend components, but never created the actual page.tsx files for three major routes. I wrote 665 lines of React across three new pages. From scratch. In a "completed" application.

3:35 PM — The URL That Went Nowhere

Bug #11: Contracts page rendered beautifully — with zero data. The API client used /contracts instead of /api/contracts. The NestJS app mounts everything under /api/. One missing prefix, complete data invisibility. Bug #12: Templates loaded but showed duplicates. The apiClient.get() already unwraps the {data} envelope. templatesApi.list() called .data again. Double-unwrap → empty or duplicated results.

3:50 PM — The Parameter Name Drift

Bug #13: Templates page — "Failed to load templates" toast. Frontend sent ?sort=name. Backend DTO expected sortBy. Validation rejected the unknown property. Frontend and backend were built by different tasks, 30 tasks apart, with no shared schema. Query parameter names
drifted. This is the speculative code problem in a nutshell — the frontend guessed what the backend would accept, and guessed wrong.

3:53 PM — The Auth Race Condition

Bug #14: This was the sneaky one. Clauses, Reports, and Approvals pages all returned 401 Unauthorized. But Dashboard and Contracts worked fine. The token getter was wired via apiClient.setTokenGetter() in the zustand store's setToken() method. On page navigation, some pages fired API calls
before the store rehydrated and wired the getter. Dashboard worked because its loading pattern was slightly different. Fix: Three-layer defense. Token getter function (primary), localStorage.getItem('auth-storage') fallback (reads zustand's persisted state directly), explicit token parameter override. Ugly. Bulletproof. Bug #15: Same moment. Clauses page — Cannot read properties of undefined (reading 'length'). The 401 from Bug #14 caused the API call to throw. The catch block showed a toast but didn't set clauses to []. Component accessed clauses.length on undefined. Bug #16: Reports page — "No reports found" despite the API having 10 reports. Same 401, plus the API double-wraps: {data: {data: [...]}}. Two bugs overlapping.

4:21 PM — The Final Three

Bug #17: Audit log page crashes on log.userId.substring(0, 8). Some audit logs are system-generated — userId is null. One missing ?. operator. Bug #18: No visible logout button. Once logged in, you're trapped. The sign-out button existed in the sidebar code but was clipped or never rendered. Bug #19: The admin hub linked to 6 sub-pages. Only 2 had page.tsx files. The other 4 returned 404.

Then Came Round 2

After fixing all 19 bugs, I ran the test suite: 30/30 API tests, 38/38 frontend tests, 3/3 Playwright E2E. All green. I declared victory again. Then I clicked into a detail page. Every single one crashed.