Production Pitfalls & AI Pipelines: A Developer's Deep Dive into Multi-Image Migrations
Join me as I recount a recent development sprint, tackling a crucial multi-image migration, verifying an ambitious AI-powered implementation pipeline, and learning some hard-won lessons about production deployments.
Every now and then, a development session comes along that perfectly encapsulates the highs and lows of building complex systems. This past week delivered just such a session. We pushed a significant feature, meticulously verified an AI-driven workflow, and – of course – stumbled into a few classic production landmines.
Let's unpack it.
The Multi-Image Revolution: More Than Just Pixels
Our primary goal for this sprint was to upgrade our note-taking system from a single-image attachment per note to a robust multi-image capability. It sounds simple on the surface, but beneath the hood, it required a thoughtful overhaul of our data model and API.
The previous setup, with imageKey and imageDescription directly on project_notes and memory_entries, was a classic case of feature creep hitting a wall. To truly unlock richer content, we needed a dedicated solution.
Here's how we tackled it:
-
A Dedicated
NoteImageModel: The core change was introducing a newNoteImagemodel inprisma/schema.prisma. This model now holds all the image-specific metadata, linking back to the parentProjectNoteorMemoryEntry.prisma// prisma/schema.prisma model NoteImage { id String @id @default(uuid()) noteId String memoryId String? // Optional, depending on where the image is attached tenantId String imageKey String description String? sortOrder Int @default(0) note ProjectNote @relation(fields: [noteId], references: [id], onDelete: Cascade) memory MemoryEntry? @relation(fields: [memoryId], references: [id], onDelete: Cascade) @@index([noteId]) @@index([memoryId]) @@unique([noteId, imageKey]) // Ensure unique image per note @@unique([memoryId, imageKey]) // Or unique image per memory } model ProjectNote { // ... existing fields ... images NoteImage[] // Removed: imageKey String?, imageDescription String? } model MemoryEntry { // ... existing fields ... images NoteImage[] // Removed: imageKey String?, imageDescription String? }Crucially, we implemented cascade deletes from both
ProjectNoteandMemoryEntry. This ensures that when a note or memory is deleted, all associated images are automatically cleaned up – preventing orphaned data and keeping our database tidy. -
API & UI Overhaul: With the data model in place, we introduced new tRPC mutations (
addImage,removeImage) within ourprojects.notesrouter. The frontend then got a shiny new multi-file drag-and-drop UI component insrc/app/(dashboard)/dashboard/projects/[id]/page.tsx, making image management intuitive. -
Data Retrieval: Our
load-notes-content.tsservice was updated to query images via the newimagesrelation, ensuring all associated visual context is loaded efficiently. -
Cleaning House: We also took the opportunity to streamline. The Memory Hub page, which previously had an image upload option, was simplified. Images are now exclusively managed within Project Notes, reducing complexity and clarifying the user experience.
This entire multi-image migration was committed in 075d9f6, deployed to production, and secured with a new RLS policy. It's a huge step forward for content richness!
The AI-Powered Pipeline: From Vision to Implementation
Beyond the multi-image upgrade, a significant chunk of our session was dedicated to verifying our "image-to-implementation" pipeline. This is where the magic of AI meets practical development. The goal? To take a high-level concept (potentially an image, a description, or a combination) and automatically generate a detailed, actionable implementation plan.
We ran workflow 8ae18aa5-a026-448e-8b92-de706296e039, titled "feat: project-onboarding (13 actions)," through its paces.
- The Engine: Powered by
google/gemini-2.5-pro, the pipeline executed 16 steps, all successfully. - Efficiency: The entire process was remarkably fast and cost-effective: just $0.82 and approximately 18 minutes.
- The Proof: The critical metric? 13 out of 13 action points matched. Every high-level enrichment action point generated by the AI had a corresponding, detailed implementation plan ready for developers.
- The Output: We ended up with a staggering 145,000 characters of implementation specifications, complete with file paths, actual code snippets, suggested tests, and even shell commands. This isn't just theory; it's a blueprint for development.
To document this success, we created a comprehensive PoC demo document: docs/reports/2026-03-15-image-to-implementation-pipeline-poc.md. It includes:
- A full pipeline architecture diagram.
- A detailed traceability chain with database table links.
- The actual execution record, including costs and durations.
- Mapping to ISO 27001 and DSGVO compliance requirements.
- Reproduction steps for anyone to follow.
This pipeline isn't just a cool demo; it's a powerful accelerator for our development process, turning abstract ideas into concrete tasks.
The Crucible of Production: Lessons Learned the Hard Way
No development session is complete without a few "learning opportunities," and this one delivered some critical insights, particularly around database management and API dependencies.
Lesson 1: The db push --accept-data-loss Trap (Again!)
This is a classic. In the heat of the moment, I mistakenly ran npx prisma db push --accept-data-loss on production.
The immediate consequence? It dropped our pgvector embedding vector(1536) column on the workflow_insights table. This is a critical column for our AI search and retrieval capabilities. This isn't the first time this has happened, and it highlights a dangerous shortcut.
The Fix: Fortunately, the fix was straightforward but manual:
ALTER TABLE workflow_insights ADD COLUMN IF NOT EXISTS embedding vector(1536);
CREATE INDEX IF NOT EXISTS workflow_insights_embedding_idx ON workflow_insights USING HNSW (embedding vector_cosine_ops);
But a manual fix on production is always a high-stress operation.
Actionable Takeaway:
NEVER run db push on production. It's designed for rapid prototyping, not for controlled, schema-evolving deployments. Always use proper migration scripts (like our ./scripts/db-migrate-safe.sh). We must implement a safety guard – whether it's a pre-push Git hook that inspects the environment or a shell alias that blocks the command on production machines. This is a critical DX improvement to prevent future headaches.
Lesson 2: The API Credit Crunch - The Unseen Dependency
Our AI pipeline relies on various LLM providers. During the Synthesis review step, we tried to use the Anthropic API, but it failed with a 400 error: "Credit balance too low."
The Impact: While the step eventually completed with an OpenAI o3 fallback, it produced empty output in this specific instance. The final "Implementation Prompt" step fortunately compensated independently, but this could have derailed the entire pipeline.
Actionable Takeaway: API credits are a hidden but critical dependency. We need a system to monitor API credit balances proactively. Furthermore, our fallback strategies need to be more robust, potentially including retry logic and verification that the fallback provider actually produces valid output. This incident underscores the importance of having multiple providers and a resilient orchestration layer.
What's Next? Paving the Way Forward
With the dust settled, we've got a clear path ahead:
- Top up Anthropic credits: Essential for the integrity of our AI review steps.
- Implement
db pushsafety guard: A pre-push hook or alias to prevent accidental production deployments. - Start implementing the AI-generated plans: Prioritizing #4 (Fix Hanging Analysis), #5 (Onboarding Wizard), and #1 (Repository Scanning).
- Thoroughly test multi-image upload on production: With fresh screenshots to ensure everything is solid.
- Wire up the Synthesis review step: Ensure it produces valid output, potentially with improved retry and provider failover logic.
This session was a microcosm of modern development: feature delivery, innovative AI integration, and the ever-present challenge of maintaining robust production systems. Each "pain" point becomes a valuable lesson, reinforcing the importance of careful planning, resilient infrastructure, and continuous improvement.
Stay tuned for more updates as we build out these exciting features!
{"thingsDone":["Migrated to multi-image upload per note","Verified AI-powered 'image-to-implementation' pipeline (13/13 actions matched)","Deployed new NoteImage model and tRPC mutations","Created PoC demo document for AI pipeline","Cleaned up Memory Hub image upload"],"pains":["Accidentally ran `npx prisma db push --accept-data-loss` on production, dropping `pgvector` column","Anthropic API failed due to low credit balance, leading to empty output from a workflow step"],"successes":["Successful multi-image migration with cascade deletes and RLS","AI pipeline generated 145K chars of implementation specs for 13 action points at low cost","Successfully restored pgvector column on production","Implemented multi-file drag-and-drop UI"],"techStack":["Prisma","tRPC","PostgreSQL","pgvector","Next.js","Google Gemini (2.5-pro)","Anthropic API","OpenAI API","Docker","Git"]}