I Shipped a Production AI Router From My Phone: Prisma, Neon Postgres, and the Termux Odyssey QuantumFlow AI founder fixed six critical bugs to seed a 26-model AI routing database from an Android phone using Termux, proot Ubuntu, and raw SQL. The engineering feat involved overcoming Prisma's incompatibility with Android's bionic libc, undefined model accessors, and a broken Prisma Client, ultimately deploying a production database ready for a Hacker News debut. How I fixed 6 critical bugs to seed a 26-model AI routing database from a Termux terminal on Android — using proot Ubuntu, raw SQL, and a lot of detective work. I run QuantumFlow AI — an AI routing platform billed as "the Stripe for AI Routing." It routes requests across 26 AI models 13 sovereign local + 13 cloud fallback , orchestrated by a bio-inspired octopus architecture with 12 quantum brains and 18 quantum arms. The live site runs on Vercel, the database on Neon PostgreSQL, and the source on a private GitHub repo. And for the last 8 hours, I did all the critical infrastructure work from my Android phone, in a Termux terminal, while sitting in a moving vehicle. This is the story of the 6 bugs that tried to stop the launch — and the forensic debugging that turned a broken Prisma connection into a fully seeded production database, ready for our Hacker News debut on Tuesday. The Setup: Termux + Next.js + Prisma + Neon Here's the stack that was supposed to work: · Device: Android phone running Termux · App: Next.js 16 monorepo quantumflow-ai-ecosystem · ORM: Prisma 6.19.3 · DB: Neon PostgreSQL serverless Postgres with a pooler endpoint · Deploy: Vercel auto-deploy on push to main · Engine: Node.js 22 LTS The plan was simple: open Termux, run npx prisma db push, run the seed script, push to GitHub, let Vercel deploy. Done in 5 minutes. It did not go that way. Bug 1: Prisma Refused to Connect on Termux The first command failed before it even reached the database: bash $ npx prisma db push prisma:warn Prisma detected unknown OS "android" and may not work as expected. Defaulting to "linux". Error: Schema engine error: That's it. No further output. Just Schema engine error: followed by silence. Root Cause Prisma's schema engine ships pre-compiled binaries for linux-arm64 glibc , darwin-arm64, and win32-x64. Termux runs Android, which uses the bionic libc instead of glibc. The linux-arm64 binary refuses to execute against bionic, so Prisma's schema engine silently crashes at startup. The prisma:warn line is the giveaway — Prisma sees process.platform === 'android', doesn't recognize it, defaults to the Linux binary, and that binary fails to load. The Fix: proot-distro Ubuntu I installed a real Linux environment inside Termux using proot-distro: pkg install proot-distro proot-distro install ubuntu proot-distro login ubuntu --bind /data/data/com.termux/files/home:/home The --bind flag mounts my Termux home directory at /home inside the Ubuntu proot, so my existing project at ~/projects/quantumflow-ai-ecosystem becomes /home/projects/quantumflow-ai-ecosystem. Inside Ubuntu, I installed Node 22 via NodeSource, ran npm install --legacy-peer-deps, and finally: bash $ npx prisma db push 🚀 Your database is now in sync with your Prisma schema. Done in 2.65s ✔ Generated Prisma Client v6.19.3 to ./node modules/@prisma/client in 2.13s Schema pushed. 164 tables created. On to the seed. Lesson: Termux is great for development, but anything that ships pre-compiled native binaries Prisma, sharp, better-sqlite3, node-gyp modules needs proot-distro to provide a glibc environment. Keep a Ubuntu proot handy — it's a 300MB one-time install that saves hours of debugging. Bug 2: prisma.aiModel Was undefined The seed script crashed on the very first line: bash $ npx tsx db/seed-intelligent.ts 🌱 QuantumFlow AI — Intelligent Database Seed v595.0.1 📦 Seeding AI Models 13 local + 15 cloud = 28 ... ❌ Seed failed: TypeError: Cannot read properties of undefined reading 'upsert' at main /home/.../db/seed-intelligent.ts:179:26 prisma.aiModel.upsert ... was failing because prisma.aiModel was undefined. But the schema had model AiModel { ... }, so Prisma should have generated prisma.aiModel — right? The Investigation I ran a diagnostic script to list every model accessor the generated Prisma Client actually exposed: bash $ npx tsx test-prisma.ts aiModel type: undefined Total models: 161 Models containing "odel": aiModelPreference globalFederatedModel mLModel modelTraining modelUpdate Wait — aiModel was undefined, but aiModelPreference existed. The client knew about other models but not the one I needed. Something was very wrong. Root Cause: A SQLite Adapter Was Hijacking the Client The smoking gun was in lib/db.ts: js // lib/db.ts — the broken version import { PrismaClient } from '@prisma/client'; import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'; const connectionString = process.env.DATABASE URL || 'file:./db/custom.db'; async function createPrismaClient { const adapterFactory = new PrismaBetterSqlite3 { url: connectionString, } ; const adapter = await adapterFactory.connect ; const prisma = new PrismaClient { adapter } ; return prisma; } export const db = await createPrismaClient ; This file was 93 versions out of date v502.2.0-TRANSCENDENT-UNITED vs the current v595.0.1 . It was written when the project used SQLite. It was still instantiating a SQLite adapter, pointing at a non-existent custom.db file, and passing that adapter to PrismaClient. The adapter override caused Prisma to ignore the entire PostgreSQL schema — so prisma.aiModel was undefined because, from the SQLite adapter's perspective, no models existed. The Fix: Strip the Adapter, Use Plain PrismaClient js // lib/db.ts — the clean version import { PrismaClient } from '@prisma/client'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma || new PrismaClient { log: process.env.NODE ENV === 'development' ? 'query', 'error', 'warn' : 'error' , } ; if process.env.NODE ENV == 'production' { globalForPrisma.prisma = prisma; } export const db = prisma; After this rewrite, prisma.aiModel came alive: bash $ npx tsx test-prisma.ts aiModel type: object Total models: 161 161 models accessible. The seed could finally run. Lesson: When a Prisma model accessor is undefined after a successful prisma generate, the problem is almost never the schema — it's how the client is being instantiated. Check lib/db.ts for adapter overrides, custom output paths, or anything that bypasses the default @prisma/client import. Bug 3: The Schema Casing Trap — AIModel vs AiModel While debugging Bug 2, I noticed something in prisma/schema.prisma: model AIModel { id String @id @default cuid name String @unique ... } model AIModelPreference { ... } Prisma generates client accessors by camelCasing the model name. For model AIModel, it generates prisma.aIModel capital A, lowercase i . For model AiModel, it generates prisma.aiModel. My seed script was calling prisma.aiModel — which only works if the schema declares model AiModel. The Fix I renamed both model declarations: model AiModel { ... } model AiModelPreference { ... } And updated the relation reference in model User: model User { ... aiModelPreferences AiModelPreference // was: AIModelPreference } Lesson: Prisma's model-name-to-accessor mapping is PascalCase → camelCase, but it's literal about the casing. AIModel becomes aIModel, not aiModel. If your codebase expects prisma.aiModel, your schema MUST say model AiModel. This is documented but easy to miss — and the error message Unknown argument doesn't immediately point to casing as the cause. Bug 4: PostgreSQL's Strict jsonb Casts The seed ran, but a chunk of it failed silently: 📦 Seeding Unified Storage entries... ⚠️ Unified storage seed skipped: PrismaClientKnownRequestError: Raw query failed. Code: 42804 . Message: ERROR: column "value" is of type jsonb but expression is of type text HINT: You will need to rewrite or cast the expression. Root Cause PostgreSQL is strict about implicit casts. The seed was using $executeRawUnsafe with parameterized SQL: await prisma.$executeRawUnsafe INSERT INTO unified storage entries key, value, tier, timestamp, ttl, metadata VALUES $1, $2, $3, $4, $5, $6 ON CONFLICT key DO UPDATE SET value = EXCLUDED.value, updated at = NOW , organismMetadata.key, JSON.stringify organismMetadata.value , // ← text, but column is jsonb 'database', organismMetadata.timestamp, null, JSON.stringify { seeded: true, version: 'v595.0.1' } // ← text, but column is jsonb ; Prisma passes JSON.stringify obj as a text parameter. The columns value and metadata are jsonb. Postgres refuses to implicitly cast text → jsonb, so it errors with code 42804. The Fix: Add Explicit ::jsonb Casts await prisma.$executeRawUnsafe INSERT INTO unified storage entries key, value, tier, timestamp, ttl, metadata VALUES $1, $2::jsonb, $3, $4, $5, $6::jsonb ON CONFLICT key DO UPDATE SET value = EXCLUDED.value, updated at = NOW , ... ; But here's the twist: a different raw SQL statement in the same seed had the opposite bug. The supreme systems table had all-TEXT columns, but the SQL was casting parameters to ::jsonb: // ❌ WRONG — these columns are TEXT, not jsonb VALUES $1, $2::jsonb, $3, $4, $5, $6::jsonb So the fix was asymmetric: · unified storage entries: ADD ::jsonb casts columns ARE jsonb · supreme systems: REMOVE ::jsonb casts columns are TEXT Lesson: When using prisma.$executeRawUnsafe with PostgreSQL jsonb columns, you must add explicit ::jsonb casts on the parameter placeholders. But always verify the column types first — casting a text parameter to jsonb on a TEXT column fails just as hard. Bug 5: The Pre-Push Hook's Unbound Variable After committing the fixes, every git push failed with: .githooks/pre-push: line 67: remote sha: unbound variable .githooks/pre-push: line 79: remote sha: unbound variable pre-push All hard gates passed. Push allowed. The push succeeded the hook was non-blocking for this path , but the errors were noisy and confusing. Root Cause The hook script started with set -euo pipefail which makes unbound variables fatal , then had: while read -r local ref local sha remote ref remote sha; do ... changed files=$ git diff --name-only --diff-filter=d "$remote sha" "$local sha" 2 /dev/null ^^^^^^^^^^^ ❌ BUG: variable is remote sha, not remote sha Someone probably an IDE auto-refactor added a leading underscore to remote sha to suppress an "unused variable" warning — without realizing the variable WAS used later, just under the wrong name. With set -u, accessing the nonexistent $remote sha triggered the unbound variable error. The Fix Rename remote sha back to remote sha sed -i 's/while read -r local ref local sha remote ref remote sha; do/while read -r local ref local sha remote ref remote sha; do/' .githooks/pre-push After the fix, two clean test pushes confirmed the bug was gone: php pre-push All hard gates passed. Push allowed. b65b9d535..70572df00 main - main Lesson: set -u treat unset variables as errors is a great safety net, but it amplifies the cost of variable-name typos. If your shell script uses set -u and a refactor renames a variable, every reference must be updated — not just the declaration. Bug 6: The QSO CLI's Phantom Commands The final bug was cosmetic but educational. My QSO CLI qso.cjs, a 3,711-line Node script with 230+ commands listed status and health in its help text: 🚀 Service Management 8 commands : start - Start frontend server tunnel - Start Cloudflare tunnel full - Start all services stop - Stop all services status - Check service status ← listed here health - Check service health ← listed here logs - Show service logs metrics - Show metrics status But running them returned: bash $ node qso.cjs status ❌ Unknown command: status ℹ️ Use "help" to see available commands Root Cause The CLI's dispatch table used an if command === '...' chain starting at line 3232. I grepped for case statements zero results — wrong pattern , then for === 'start' zero results — wrong quotes , then finally for args 0 and found the actual dispatch at line 3232: js const command = args 0 ?.toLowerCase ; if command === 'g-commit' || command === 'gc' { ... } if command === 'version' || command === '--version' || command === '-v' { ... } if command === 'help' || command === '--help' || command === '-h' || command { ... } // ... 200+ more if-blocks ... if command === 'dev:status' return this.ecosystemEngine.devStatus ; // ... // Unknown command fallback console.log Unknown command: ${command} ; The status and health cases were simply missing from the chain. The help text was generated separately probably copy-pasted from an older version and listed commands that the dispatch table never registered. The Fix A one-line sed insertion right before the "Unknown command" fallback: sed -i '/^ \/\/ Unknown command$/i\ if command === "status" || command === "health" { return this.ecosystemEngine.devStatus ; }' qso.cjs After the fix: bash $ node qso.cjs status 📊 DEV ECOSYSTEM STATUS ⚡ PM2 STATUS ┌────┬───────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ └────┴───────────┴──────────┴──────┴───────────┴──────────┴──────────┘ 🌐 CADDY STATUS ✅ Caddy: RUNNING on :81 Lesson: Help text and dispatch tables drift apart over time. If your CLI has more than 50 commands, write a test that runs every command listed in the help text and asserts none return "Unknown command". I'll be adding that test next week. The Final State After fixing all 6 bugs, the database seeded cleanly: Component Count AI models 26 13 local PRIMARY + 13 cloud fallback Quantum Brains 9 Quantum Arms 18 Supreme Systems 13 Emergent Capabilities 13 Total tables 164 Production health check from my cloud sandbox: bash $ curl -sS https://quantumflow-ai-ecosystem.vercel.app/api/health { "status": "healthy", "version": "v595.0.1", "uptime": 4, "checks": {"name": "edge-runtime", "status": "pass"}, {"name": "quantum-coherence", "status": "pass"}, {"name": "api-gateway", "status": "pass"} , "rateLimiting": { "enabled": true, "upstashEnabled": true, "categories": { "auth": {"limit": 5, "window": 60, "algorithm": "sliding"}, "ai-operations": {"limit": 10, "window": 60, "algorithm": "tokenBucket"}, ... } } } Security headers — all 10 critical headers present: strict-transport-security: max-age=31536000; includeSubDomains; preload content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline' ... cross-origin-embedder-policy: credentialless cross-origin-opener-policy: same-origin cross-origin-resource-policy: same-site permissions-policy: camera= , microphone= , geolocation= , payment= referrer-policy: origin-when-cross-origin x-content-type-options: nosniff x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block 4 commits shipped in one session: a3d1b7749 fix qso+hooks : add status/health commands + fix pre-push unbound var 70572df00 test: pre-push hook fix verification b65b9d535 test: pre-push hook fix verification 271304c66 chore: cleanup working tree + preserve audit artifacts 2dc3fb77a fix db : PostgreSQL Prisma client + jsonb casts + AiModel casing 789c4f003 feat db : intelligent seed — 28 AI models + organism metadata The Meta-Lesson: Debugging Is Forensics What made this session work wasn't any single fix — it was the discipline of treating each bug as a forensic investigation: What's Next The database layer is production-certified. The HN launch is Tuesday 8–10am ET. After we land our first 5 customers, the next milestone is adding DeepSeek V3.1 via API ~9× cheaper than GPT-4o as a cloud fallback model — customer-funded development is the right discipline. If you want to follow along, the live site is at https://quantumflow-ai-ecosystem.vercel.app https://quantumflow-ai-ecosystem.vercel.app , and I'll be posting the HN link to @AetheriusFlow when it goes live. Building something wild from your phone? I'd love to hear about it. Drop a comment below or find me on X — I read everything.