HANDOFF SESSION 100 VITTO FIXES

Documentation for Handoffs

HANDOFF: Session 100 — Vitto API Production Fixes


Handoff ID: SESSION-100-VITTO-FIXES From: Cowork Agent (Browser/Infrastructure — Paco's Cowork session) To: Cursor / Coding Agents Priority: HIGH Created: 2026-02-10 Session: 100 (continued) Status: READY FOR PICKUP Type: Bug Fixes — Vitto API Production Issues Confidence: 0.95


Context

After successfully deploying backend-vitto-api to Railway (deployment e468bde0, commit 077acde4f), the following runtime issues were observed in the deploy logs. These need code-level fixes before the next deployment.

Current Deploy Logs (Feb 9, 2026, 10:30 PM CST)

Starting Container
(node:1) [MONGOOSE] Warning: Duplicate schema index on {"expiresAt":1} found.
(node:1) [MONGOOSE] Warning: Duplicate schema index on {"userId":1} found.
[RouteDiscovery] Discovered 32 routes in 6 modules
🤖 VITTO Super-Agent API READY on port 3003 (Railway)
[VITTO-API] 1  - 02/10/2026, 4:40:00 AM  WARN [ProactiveMonitor] [api-health] Alert: Services unreachable: backend-api, backend-ai-api, backend-docs-api
[VITTO-API] 1  - 02/10/2026, 4:45:00 AM  WARN [ProactiveMonitor] [api-health] Alert: Services unreachable: backend-api, backend-ai-api, backend-docs-api
[VITTO-API] 1  - 02/10/2026, 4:45:00 AM  WARN [ProactiveMonitor] [space-agents-health] Alert: Agent types unavailable: code, browser (0 agents online)
[VITTO-API] 1  - 02/10/2026, 4:50:00 AM  WARN [ProactiveMonitor] [api-health] Alert: Services unreachable: backend-api, backend-ai-api, backend-docs-api

Health Status (verified)

Service Endpoint Status
backend-api https://api.gograb.space/api/health ✅ 200 OK
backend-ai-api https://ai-api.gograb.space/ai-api/health ✅ 200 OK
backend-docs-api https://docs-api.gograb.space/docs-api/health ✅ 200 OK
backend-vitto-api https://vitto-api.gograb.space/vitto-api/health ✅ 200 OK

All 4 services are healthy and responding. The warnings are caused by incorrect health check URL construction in the Vitto codebase, not by actual service outages.


Issue 1: ProactiveMonitor Health Check URLs Are Wrong (🔴 CRITICAL)

Logs:

WARN [ProactiveMonitor] [api-health] Alert: Services unreachable: backend-api, backend-ai-api, backend-docs-api

Frequency: Every 5 minutes, continuously since startup

Root Cause Analysis

The three backend client services construct health check URLs by stripping the API path prefix, which produces incorrect URLs when using the public domain names.

Current Railway Environment Variables (Vitto API):

BACKEND_API_URL="https://api.gograb.space/api"
BACKEND_AI_API_URL="https://ai-api.gograb.space/ai-api"
BACKEND_DOCS_API_URL="https://docs-api.gograb.space/docs-api"

What the health checks do vs. what they should do:

Service Code Does Resulting URL Actual Health Endpoint Match?
backend-api baseUrl.replace('/api', '') + '/health' https://api.gograb.space/health https://api.gograb.space/api/health ❌ 404
backend-ai-api baseUrl.replace('/ai-api', '') + '/health/unified-health' https://ai-api.gograb.space/health/unified-health https://ai-api.gograb.space/ai-api/health ❌ 404
backend-docs-api baseUrl.replace('/docs-api', '') + '/health' https://docs-api.gograb.space/health https://docs-api.gograb.space/docs-api/health ❌ 404

Files to Fix

File 1: apis/backend-vitto-api/src/app/connectivity/backend-api-client.service.ts

// CURRENT (BROKEN):
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl.replace('/api', '')}/health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

// FIX — use the actual health endpoint:
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl}/health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

File 2: apis/backend-vitto-api/src/app/connectivity/backend-ai-client.service.ts

// CURRENT (BROKEN):
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl.replace('/ai-api', '')}/health/unified-health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

// FIX — use the actual health endpoint:
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl}/health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

File 3: apis/backend-vitto-api/src/app/connectivity/backend-docs-client.service.ts

// CURRENT (BROKEN):
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl.replace('/docs-api', '')}/health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

// FIX — use the actual health endpoint:
async checkHealth(): Promise<boolean> {
  try {
    await firstValueFrom(
      this.http.get(`${this.baseUrl}/health`, { headers: this.headers, timeout: 5000 }),
    );
    return true;
  } catch { return false; }
}

Why This Fix Works

The env vars already include the path prefix (e.g., https://api.gograb.space/api), so the health endpoint is simply ${baseUrl}/health = https://api.gograb.space/api/health — which is exactly what returns 200 OK.

Also Consider

The BACKEND_API_INTERNAL_TOKEN env var is NOT configured in Railway. The backend clients use it as a Bearer token in the Authorization header. If health endpoints require auth, this needs to be set. Currently it defaults to empty string.


Issue 2: Space Agents Health Check — No Agents Online (🟡 MEDIUM)

Logs:

WARN [ProactiveMonitor] [space-agents-health] Alert: Agent types unavailable: code, browser (0 agents online)

Frequency: Every 15 minutes

Root Cause

The space-agents-health check calls BackendAiClientService.getLiveAgents() which hits GET ${BACKEND_AI_API_URL}/mcp/agents. It expects to find agents of type code and browser registered with the ai-api.

Currently, 0 agents are online because:

  1. No MCP code agents or browser agents have registered with ai-api
  2. The MCP circuit breaker on ai-api is permanently open (Issue 1 in the parent handoff HANDOFF-SESSION-100-PRODUCTION-ISSUES.md)

Files

Fix Options

  1. Short-term: Make the agent check non-critical — log at INFO level instead of WARN when no agents are expected in production
  2. Long-term: Deploy MCP agent servers that register with ai-api, or configure MCP integration properly (see parent handoff Issue 1)
  3. Defensive code: Add a config flag REQUIRE_AGENTS=false to disable this check when agents aren't expected

Issue 3: Mongoose Duplicate Schema Indexes (🟡 MEDIUM)

Logs:

[MONGOOSE] Warning: Duplicate schema index on {"expiresAt":1} found.
[MONGOOSE] Warning: Duplicate schema index on {"userId":1} found.

Root Cause

In the UserAgentMemorySchema, the expiresAt field has BOTH an inline index AND a separate schema.index() call:

File: libs/libs-api/backend-core/src/lib/schemas/user-agent-memory.schema.ts

// Line 122-123: Inline index on the field
@Prop({ type: Date, index: true })
expiresAt?: Date;

// Line 153: DUPLICATE — separate index call on the same field
UserAgentMemorySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });

Fix

Remove index: true from the @Prop decorator since the schema.index() call is needed for the TTL expireAfterSeconds option:

// Line 122-123: Remove index: true
@Prop({ type: Date })
expiresAt?: Date;

// Line 153: Keep this — it defines the TTL index
UserAgentMemorySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });

For userId: Check if there's a compound index that includes userId — if so, remove the index: true from the field definition to avoid the duplicate. The compound indexes at lines 144, 147, 150, 156, 159 already cover userId.


Issue 4: Missing BACKEND_API_INTERNAL_TOKEN Environment Variable (🟡 MEDIUM)

The three backend client services (BackendApiClientService, BackendAiClientService, BackendDocsClientService) all read BACKEND_API_INTERNAL_TOKEN for authenticating internal API calls between services.

Current state: Not configured in Railway → defaults to empty string → Authorization: Bearer header sent with all requests.

Fix

  1. Generate a shared internal token
  2. Add BACKEND_API_INTERNAL_TOKEN=<token> to Railway env vars for:
    • backend-vitto-api
    • backend-ai-api (if it validates this token)
    • backend-api (if it validates this token)
    • backend-docs-api (if it validates this token)

OR: If the health endpoints don't require auth (they typically don't), this may be lower priority. But other internal API calls between services likely need this token.


Issue 5: Remove Temporary DEPLOY_TRIGGER Variable (🟢 LOW)

A temporary DEPLOY_TRIGGER="1" environment variable exists in Railway for the Vitto API service. It was added to force a redeployment and is harmless but should be cleaned up.

Action: Remove DEPLOY_TRIGGER from Railway backend-vitto-api environment variables.


Summary: Action Priority Matrix

# Issue Severity Type Effort
1 ProactiveMonitor health check URLs wrong 🔴 CRITICAL Code Fix (3 files) ~15 min
2 Space agents health — no agents online 🟡 MEDIUM Code Fix / Config ~30 min
3 Mongoose duplicate schema indexes 🟡 MEDIUM Schema Fix (1 file) ~10 min
4 Missing BACKEND_API_INTERNAL_TOKEN 🟡 MEDIUM Env Var ~5 min
5 Remove DEPLOY_TRIGGER env var 🟢 LOW Cleanup ~1 min

Related Handoffs


Railway Reference

Key Value
Project ID 8d9b4864-25f4-4c4e-b479-f5e49cea4001
Service ID 1acab221-cd3e-4c3f-9cbb-bcbc5feb7954
Environment ID 0d84c89d-6bc7-425c-8d17-14e0a5760041
Branch deploy/vitto-api
Active Deployment e468bde0 (commit 077acde4f)
Port 3003
Domain vitto-api.gograb.space

Verification After Fixes

After deploying the code fixes, verify:

  1. Health check logs should stop — No more [api-health] Alert: Services unreachable warnings
  2. Mongoose warnings should stop — No more duplicate index warnings on startup
  3. Health endpoint still works — https://vitto-api.gograb.space/vitto-api/health returns 200 OK
  4. ProactiveMonitor status — Call the ProactiveMonitor status endpoint to verify checks pass