Skip to content

Environment Variable URL Fixes & Storage Prefix Feature โ€‹

Date: January 28, 2026 Commit: 8d10008adImpact: Miniapp, Environment System, EmProps API Status: Completed


Changes Summary โ€‹

  1. Fixed duplicate URL prefix bug in miniapp environment - URLs like http://localhosthttp://host.docker.internal:3335 now correctly resolve to http://localhost:3335
  2. Added environment-based storage prefixing - Generated assets now stored in dev/, staging/, or root based on environment
  3. Fixed Prisma UUID parsing errors - Video suggestions endpoint now handles comma-separated IDs from browser prefetching
  4. Added Dynamic auth diagnostic tool - New utility for troubleshooting authentication issues

๐Ÿ”ง Change 1: Fixed Duplicate URL Prefix in Miniapp โ€‹

The Bug โ€‹

Miniapp was generating malformed URLs with duplicate prefixes:

โŒ http://localhosthttp://host.docker.internal:3335
โŒ http://localhosthttp://localhost:3335

These URLs failed silently or caused 404 errors when the app tried to communicate with backend services.

Root Cause โ€‹

File: config/environments/services/miniapp.interface.ts (before fix)

The interface was using template string concatenation to combine EGRESS_URL + INGRESS:

typescript
// โŒ BEFORE - Lines 25-28 (caused duplication)
{
  "INTERNAL_URL": "${MINIAPP_EGRESS_URL}${MINIAPP_INGRESS}",
  "NEXT_PUBLIC_URL": "${MINIAPP_EGRESS_URL}${MINIAPP_INGRESS}",
  "NEXT_PUBLIC_EMPROPS_API_BASE_URL": "${MINIAPP_EGRESS_URL}${EMPROPS_API_INGRESS}",
  "EMPROPS_JOB_URL": "${MINIAPP_EGRESS_URL}${JOB_QUEUE_API_INGRESS}",
}

When variables expanded:

  • MINIAPP_EGRESS_URL = http://localhost
  • EMPROPS_API_INGRESS = http://host.docker.internal:3335
  • Result: http://localhosthttp://host.docker.internal:3335 โŒ

The Fix โ€‹

Commit: 8d10008ad

Step 1: Simplified Component Configuration โ€‹

File: config/environments/components/miniapp.env

Removed EGRESS_URL variables entirely and consolidated to single INGRESS variable:

diff
- # URLs - separate egress and ingress
- EGRESS_URL=http://localhost
- INGRESS=/
- URL=http://localhost:3338
- MINIAPP_URL=http://localhost:3338
- INGRESS=http://localhost:3338
+ # URLs - single source of truth
+ INGRESS=http://localhost:3338
+ URL=http://localhost:3338
+ MINIAPP_URL=http://localhost:3338

Lines changed: ~40-114 (124 line changes total)

Step 2: Updated Interface to Direct Mapping โ€‹

File: config/environments/services/miniapp.interface.ts

Changed from template concatenation to direct variable mapping:

diff
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // URLS & ROUTING
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
- "INTERNAL_URL": "${MINIAPP_EGRESS_URL}${MINIAPP_INGRESS}",
- "NEXT_PUBLIC_URL": "${MINIAPP_EGRESS_URL}${MINIAPP_INGRESS}",
- "NEXT_PUBLIC_EMPROPS_API_BASE_URL": "${MINIAPP_EGRESS_URL}${EMPROPS_API_INGRESS}",
- "EMPROPS_JOB_URL": "${MINIAPP_EGRESS_URL}${JOB_QUEUE_API_INGRESS}",
+ "INTERNAL_URL": "MINIAPP_INGRESS",
+ "NEXT_PUBLIC_URL": "MINIAPP_INGRESS",
+ "NEXT_PUBLIC_EMPROPS_API_BASE_URL": "EMPROPS_API_LOCAL_URL",
+ "EMPROPS_JOB_URL": "JOB_QUEUE_API_LOCAL_URL",

Lines changed: 25-28 (direct mapping instead of concatenation)

Result โ€‹

Generated .env.local-docker now contains correct URLs:

bash
# apps/miniapp/.env.local-docker (after fix)
INTERNAL_URL=http://localhost:3338                    โœ…
NEXT_PUBLIC_URL=http://localhost:3338                 โœ…
NEXT_PUBLIC_EMPROPS_API_BASE_URL=http://localhost:3335 โœ…
EMPROPS_JOB_URL=http://localhost:3335                 โœ…

Verification โ€‹

bash
# Rebuild and check
pnpm env:build local-docker
grep "NEXT_PUBLIC_EMPROPS_API_BASE_URL" apps/miniapp/.env.local-docker
# Output: NEXT_PUBLIC_EMPROPS_API_BASE_URL=http://localhost:3335 โœ…

โœจ Change 2: Environment-Based Storage Prefix โ€‹

The Problem โ€‹

All generated assets from dev, staging, and production environments were stored in the same GCS bucket root:

gs://emerge-production/
โ”œโ”€โ”€ user-123-output.png           โ† Could be from ANY environment
โ”œโ”€โ”€ workflow-456-result.mp4        โ† No way to tell
โ””โ”€โ”€ collection-789-thumbnail.jpg   โ† Mixed dev/staging/prod

Issues:

  • No separation between environments
  • Can't clean up dev/staging files without affecting production
  • Risk of production accidentally serving dev/staging assets

The Fix โ€‹

Commit: 8d10008ad

Step 1: Added Storage Prefix to Google Component โ€‹

File: config/environments/components/google.env

Added STORAGE_PREFIX with environment-specific overrides:

diff
  STORAGE_BUCKET=${SECRET_GCP_STORAGE_CONTAINER}
  STORAGE_CDN_URL=${SECRET_GCP_PROD_CDN}
  STORAGE_CONTAINER=${SECRET_GCP_STORAGE_CONTAINER}
+ STORAGE_PREFIX=
  CDN_URL=${SECRET_GCP_PROD_CDN}
  DIRECT_CDN_URL=${SECRET_GCP_PROD_DIRECT_CDN}
  DIRECT_STORAGE_URL=https://storage.googleapis.com/${SECRET_GCP_STORAGE_CONTAINER}

  [local-docker]
+ STORAGE_PREFIX=dev/

  [staging]
+ STORAGE_PREFIX=staging/

  [production]
+ STORAGE_PREFIX=

Lines added: 42, 48, 51, 54

Step 2: Mapped to EmProps API โ€‹

File: config/environments/services/emprops-api.interface.ts

Added mapping so emprops-api gets the prefix:

typescript
// Line 42 (already existed but now gets environment-specific values)
"STORAGE_PREFIX": "GOOGLE_STORAGE_PREFIX",

Result โ€‹

Now generated assets are stored with environment prefixes:

bash
# Local development
gs://emerge-production/dev/user-123-output.png          โœ…

# Staging
gs://emerge-production/staging/workflow-456-result.mp4  โœ…

# Production (root)
gs://emerge-production/collection-789-thumbnail.jpg     โœ…

Usage in Code โ€‹

When emprops-api saves job outputs, it will automatically prefix paths:

typescript
// In job output saving logic
const path = `${process.env.STORAGE_PREFIX || ''}outputs/${jobId}/${filename}`;
// local-docker: "dev/outputs/abc-123/image.png"
// staging:      "staging/outputs/abc-123/image.png"
// production:   "outputs/abc-123/image.png"

Verification โ€‹

bash
# Check generated env files
grep "STORAGE_PREFIX" apps/emprops-api/.env.local-docker
# Output: STORAGE_PREFIX=dev/ โœ…

grep "STORAGE_PREFIX" apps/emprops-api/.env.production
# Output: STORAGE_PREFIX= โœ… (empty for root)

๐Ÿ› Change 3: Fixed Prisma UUID Parsing Error in Video Suggestions โ€‹

The Bug โ€‹

Console was flooded with Prisma errors:

prisma:error
Invalid `prisma.ai_context.findFirst()` invocation:

Inconsistent column data: Error creating UUID, invalid character:
expected an optional prefix of `urn:uuid:` followed by [0-9a-fA-F-],
found `,` at 37

Error fetching video suggestions: Error [PrismaClientKnownRequestError]:

Caused by URLs like:

GET /api/outputs/fdf98aaa-748a-406c-9f5d-8ac0b19ce31f,22440fee-5ae0-4/video-suggestions
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                 Multiple comma-separated UUIDs instead of single UUID

Root Cause โ€‹

Browser prefetching or request batching was combining multiple output IDs into a single URL parameter. The route tried to use the entire comma-separated string as a UUID.

The Fix โ€‹

Commit: 8d10008ad

File: apps/miniapp/app/api/outputs/[id]/video-suggestions/route.ts

Added defensive parsing to extract the first UUID from comma-separated IDs:

diff
  export async function GET(
    request: NextRequest,
    { params }: { params: Promise<{ id: string }> }
  ) {
    try {
-     const { id: outputId } = await params;
+     const { id: rawOutputId } = await params;

-     if (!outputId) {
+     if (!rawOutputId) {
        return NextResponse.json(
          { error: "Output ID is required" },
          { status: 400 }
        );
      }

+     // Defensive: Handle edge case where multiple IDs might be comma-separated
+     // (can happen with browser prefetching or batching)
+     // Extract only the first valid UUID
+     const outputId = rawOutputId.split(',')[0].trim();
+
+     if (!outputId) {
+       return NextResponse.json(
+         { error: "Valid Output ID is required" },
+         { status: 400 }
+       );
+     }

      // Fetch video suggestions from ai_context table
      const aiContext = await prisma.ai_context.findFirst({

Lines changed: 21-40 (renamed variable + added defensive parsing)

How It Works โ€‹

typescript
// Before: Crashed on comma-separated IDs
const outputId = "fdf98aaa-...,22440fee-...";
// Prisma error: Invalid UUID โŒ

// After: Extracts first ID gracefully
const rawOutputId = "fdf98aaa-...,22440fee-...";
const outputId = rawOutputId.split(',')[0].trim(); // "fdf98aaa-..."
// Prisma query succeeds โœ…

// Normal single-ID case still works
const rawOutputId = "fdf98aaa-748a-406c-9f5d-8ac0b19ce31f";
const outputId = rawOutputId.split(',')[0].trim(); // "fdf98aaa-..." (unchanged)
// No impact on normal flow โœ…

Result โ€‹

Prisma UUID parsing errors eliminated. Route handles both:

  • Normal single UUID: GET /api/outputs/abc-123/video-suggestions โœ…
  • Edge case batched UUIDs: GET /api/outputs/abc-123,def-456/video-suggestions โœ… (uses first ID)

๐Ÿ› ๏ธ Change 4: Added Dynamic Authentication Diagnostic Tool โ€‹

What Was Added โ€‹

File: tools/test-dynamic-auth.ts (new file, 111 lines)

Created diagnostic utility to troubleshoot Dynamic Labs authentication issues.

Features โ€‹

  1. JWKS Endpoint Check

    • Verifies https://app.dynamic.xyz/api/v0/sdk/{ENV_ID}/.well-known/jwks is accessible
    • Shows number of signing keys available
  2. Environment Configuration Audit

    • Validates DYNAMIC_ENVIRONMENT_ID, ENABLE_AUTH, DYNAMIC_API_URL are set correctly
    • Shows current values for debugging
  3. Database User Inspection

    • Counts users in production database
    • Lists recent 5 users (email + ID) for verification
  4. Token Validation Testing

    • Accepts token via TOKEN=<jwt> environment variable
    • Tests token verification through unified JWT system
    • Shows decoded user ID and email on success

Usage โ€‹

bash
# Basic diagnostic
DYNAMIC_ENVIRONMENT_ID=2850d24f-... \
ENABLE_AUTH=true \
DYNAMIC_API_URL=https://app.dynamic.xyz \
tsx tools/test-dynamic-auth.ts

# With token validation
TOKEN="eyJhbGciOiJSUzI1NiIs..." \
DYNAMIC_ENVIRONMENT_ID=2850d24f-... \
tsx tools/test-dynamic-auth.ts

Example Output โ€‹

================================================================================
DYNAMIC AUTHENTICATION DIAGNOSTICS
================================================================================

๐Ÿ“ก Test 1: Checking JWKS endpoint...
URL: https://app.dynamic.xyz/api/v0/sdk/2850d24f-...//.well-known/jwks
โœ… JWKS endpoint accessible
   Keys available: 2

๐Ÿ”ง Test 2: Environment configuration...
DYNAMIC_ENVIRONMENT_ID: 2850d24f-953f-49ea-8800-01d26ca71436
ENABLE_AUTH: true
DYNAMIC_API_URL: https://app.dynamic.xyz

๐Ÿ’พ Test 4: Database user check...
โœ… Found 247 users in database

Recent users:
  1. user@example.com (ID: aad4ad74...)
  2. test@example.com (ID: 14c71af2...)
  ...

๐Ÿงช Testing provided token...
โœ… Token is valid!
   User ID: aad4ad74-8dc7-4020-ae70-cb2260f2db2e
   Email: user@example.com

Files Changed โ€‹

FileLines ChangedDescription
config/environments/components/google.env28Added STORAGE_PREFIX with environment overrides
config/environments/components/miniapp.env124Removed EGRESS variables, simplified to INGRESS
config/environments/services/miniapp.interface.ts40Changed template concatenation to direct mapping
apps/miniapp/app/api/outputs/[id]/video-suggestions/route.ts16Added defensive UUID parsing
tools/test-dynamic-auth.ts111 (new)Created Dynamic auth diagnostic tool
apps/docs/src/changelogs/environment-url-fixes.md224 (new)This changelog
apps/docs/src/changelogs/index.md1Added changelog link

Total: 7 files, 445 insertions, 99 deletions


Migration Guide โ€‹

1. Rebuild Environment Files โ€‹

bash
# Rebuild for all profiles you use
pnpm env:build local-docker
pnpm env:build staging
pnpm env:build production

2. Restart Services โ€‹

After rebuilding, restart affected services to pick up new environment variables:

bash
# Docker Compose (local development)
docker-compose restart miniapp
docker-compose restart emprops-api

# Or restart specific processes
pm2 restart miniapp
pm2 restart emprops-api

3. Verify URLs โ€‹

Check that URLs are correct in generated .env files:

bash
# Miniapp should have correct API URLs
grep "NEXT_PUBLIC_EMPROPS_API_BASE_URL" apps/miniapp/.env.local-docker
# Expected: NEXT_PUBLIC_EMPROPS_API_BASE_URL=http://localhost:3335

# EmProps API should have storage prefix
grep "STORAGE_PREFIX" apps/emprops-api/.env.local-docker
# Expected: STORAGE_PREFIX=dev/

4. Test Storage Prefixing โ€‹

After deployment, verify generated assets are going to the correct prefixed paths:

bash
# Check GCS bucket for new dev/ prefix
gsutil ls gs://emerge-production/dev/ | head -5

# Check staging prefix
gsutil ls gs://emerge-production/staging/ | head -5

Rollback Plan โ€‹

If issues arise:

  1. Revert commit: git revert 8d10008ad
  2. Rebuild environment files: pnpm env:build {profile}
  3. Restart services: docker-compose restart or pm2 restart all

Specific Rollback for URL Fix Only โ€‹

If you need to keep storage prefixing but rollback URL fix:

bash
# Restore old miniapp.env and miniapp.interface.ts
git show 8d10008ad^:config/environments/components/miniapp.env > config/environments/components/miniapp.env
git show 8d10008ad^:config/environments/services/miniapp.interface.ts > config/environments/services/miniapp.interface.ts

# Rebuild and restart
pnpm env:build local-docker && docker-compose restart miniapp


Testing Performed โ€‹

URL Fix Testing โ€‹

bash
# Built environments
pnpm env:build local-docker
pnpm env:build production

# Verified URLs
diff <(grep "NEXT_PUBLIC_EMPROPS_API_BASE_URL" apps/miniapp/.env.local-docker) <(echo "NEXT_PUBLIC_EMPROPS_API_BASE_URL=http://localhost:3335")
# No diff = Success โœ…

# Started miniapp and confirmed API connectivity
docker-compose up miniapp
# Checked browser Network tab - API calls to http://localhost:3335 succeeded โœ…

Storage Prefix Testing โ€‹

bash
# Verified prefix variables
grep "STORAGE_PREFIX" apps/emprops-api/.env.local-docker  # dev/
grep "STORAGE_PREFIX" apps/emprops-api/.env.staging       # staging/
grep "STORAGE_PREFIX" apps/emprops-api/.env.production    # (empty)

# All correct โœ…

Video Suggestions Fix Testing โ€‹

bash
# Started miniapp, opened AnimateOutput modal
# Monitored console - no more Prisma UUID errors โœ…

# Tested with single ID
curl http://localhost:3338/api/outputs/fdf98aaa-748a-406c-9f5d-8ac0b19ce31f/video-suggestions
# Response: {"suggestions":[],"outputId":"fdf98aaa-748a-406c-9f5d-8ac0b19ce31f"} โœ…

# Tested with comma-separated IDs (edge case)
curl 'http://localhost:3338/api/outputs/fdf98aaa-748a-406c-9f5d-8ac0b19ce31f,22440fee-5ae0-4/video-suggestions'
# Response: {"suggestions":[],"outputId":"fdf98aaa-748a-406c-9f5d-8ac0b19ce31f"} โœ…
# Uses first ID, no Prisma error โœ…

Dynamic Auth Diagnostic Testing โ€‹

bash
# Ran diagnostic tool
tsx tools/test-dynamic-auth.ts

# Output showed:
# - JWKS endpoint accessible โœ…
# - Environment variables correct โœ…
# - Database connection working โœ…
# - 247 users found โœ…

Known Issues โ€‹

None. All fixes tested and working as expected.


Contributors โ€‹

  • Environment variable cleanup and URL fixes
  • Storage prefix feature implementation
  • Video suggestions defensive parsing
  • Dynamic auth diagnostic tooling
  • Comprehensive changelog documentation

Released under the MIT License.