# P0 Bugs Investigation - Jan 29, 2026

> **Architect Investigation Report**
> Bugs reported in #growth-alerts Slack channel

**Cross-reference:** `docs/bugs-investigation-plan.md` (Jan 23 bugs - 19 total)

## Status of Today's Bugs

| Bug | New? | Status |
|-----|------|--------|
| #1 Inbound SMS not updating leads | **NEW** | Investigated - root cause found |
| #2 Inbound calls not logging | **NEW** | Investigated - root cause found |
| #3 Mobile scroll triggers click | Existing (Bug #7) | Already documented Jan 23 |
| #4 Messages not showing (Fitzgerald) | **NEW** | Needs DB query to verify |

---

## Bug #1: Inbound SMS Not Updating Leads (Tony, Paddy)

### Impact
**Missed hot leads** - When SMS comes in, lead record doesn't update with:
- `last_contact_at`
- `last_inbound_at`
- `metadata.last_message`
- Status changes (positive → engaged)

### Root Cause Analysis

**File:** `src/routes/webhooks.js` lines 267-293

**Problem 1: Silent Failure - No Error Handling**
```javascript
// Lines 289-293 - NO ERROR CAPTURE
await supabase
  .from('leads')
  .update(updates)
  .eq('id', lead.id);
// ← No { error } capture, no logging, fails silently!
```

Compare to `src/routes/voice.js` lines 154-185 which DOES have proper error handling:
```javascript
const { error: updateError } = await supabase
  .from('leads')
  .update({...})
  .eq('id', lead.id);

if (updateError) {
  console.error('[Voice Inbound] Failed to update lead score:', updateError.message);
}
```

**Problem 2: Phone Lookup May Fail Silently**
- Lines 62-97 try multiple phone formats (E.164, local, short)
- If no format matches, `lead = null` and update block never runs
- Only log on lines 99 and 101, but no alert/warning

**Problem 3: Lead Update Block Only Runs If Lead Found**
- Line 267: `if (lead && supabase)`
- If phone lookup fails, the entire update is skipped silently

### Evidence
The webhook:
1. ✅ Logs to communications (has error handling lines 173-177)
2. ✅ Updates buying signals in lead metadata (lines 180-202)
3. ❌ Updates lead contact fields (NO error handling lines 289-293)

### Recommended Fixes

**Fix 1: Add Error Handling to Lead Update**
```javascript
// Replace lines 289-293 with:
const { error: leadUpdateError } = await supabase
  .from('leads')
  .update(updates)
  .eq('id', lead.id);

if (leadUpdateError) {
  console.error(`[Webhook] Failed to update lead ${lead.id}:`, leadUpdateError.message);
} else {
  console.log(`[Webhook] Lead ${lead.id} updated - last_contact_at: ${now}`);
}
```

**Fix 2: Add Warning When Lead Not Found**
```javascript
// After line 102, add:
if (!lead) {
  console.warn(`[Webhook] ⚠️ Inbound SMS from unknown number: ${normalizedPhone || From}`);
  // Could also create a lead here, or at least track the orphan SMS
}
```

**Fix 3: Add Slack Alert for Failed Updates**
When an inbound SMS with high buying signal fails to update its lead, send Slack alert.

---

## Bug #2: Inbound Calls Not Logging

### Current State Analysis

**File:** `src/routes/voice.js` lines 107-255

The inbound call handler DOES log to communications (lines 192-218):
```javascript
const { error: commError } = await supabase.from('communications').insert({
  lead_id: lead?.id || null,
  type: 'call_inbound',
  ...
});

if (commError) {
  console.error('[Voice Inbound] Failed to log communication:', commError.message);
}
```

This has proper error handling. So the bug may be elsewhere.

### Possible Causes

1. **Outcome Logging Missing** - When inbound call ENDS, the outcome (connected, voicemail, etc.) may not be logged
2. **Frontend Not Showing CallOutcomeSheet** - Per LESSONS.md, this was fixed but may have regressed
3. **Call Duration Not Captured** - Inbound calls may not have duration tracked

### Root Cause Analysis (Completed)

**File:** `admin/src/components/IncomingCallHandler.jsx`

**Problem 1: Race Condition Between Disconnect and Outcome Sheet**
```javascript
// Lines 230-236 - disconnect handler resets after 500ms
call.on('disconnect', () => {
  setCallStatus('ended');
  setTimeout(() => {
    resetCallState();  // ← Resets showOutcomeSheet to false!
  }, 500);
});
```

The `onCallComplete` callback (which shows the outcome sheet) may fire AFTER the 500ms reset, causing the outcome sheet to never appear.

**Problem 2: Unknown Callers Never Get Outcome Logged**
```javascript
// Lines 288-296 - callback only shows sheet if leadData exists
acceptInboundCall(callRef.current, leadData, (duration) => {
  if (leadData) {
    setCallEndedLead(leadData);
    setShowOutcomeSheet(true);
  } else {
    resetCallState();  // ← Unknown callers skip outcome logging!
  }
});
```

If the caller's phone number doesn't match any lead, `leadData` is null, and the outcome sheet is NEVER shown. The call is not logged with an outcome.

**Problem 3: Backend Inbound Logging Works But Frontend Outcome Missing**
- Backend (`voice.js` lines 192-218) logs `call_inbound` when call ARRIVES
- But **outcome** (connected, voicemail, etc.) is only logged when user saves CallOutcomeSheet
- If CallOutcomeSheet never shows, the call has no outcome in the database

### Recommended Fixes

**Fix 1: Remove Competing Disconnect Handler**
```javascript
// In IncomingCallHandler.jsx, lines 230-236
// REMOVE the setTimeout resetCallState - let onCallComplete handle it
call.on('disconnect', () => {
  console.log('[IncomingCall] Call disconnected');
  setCallStatus('ended');
  // DON'T reset here - let onCallComplete callback handle state
});
```

**Fix 2: Always Show Outcome Sheet for Inbound Calls**
```javascript
// Lines 288-296 - always show outcome sheet
acceptInboundCall(callRef.current, leadData, (duration) => {
  // Always show outcome sheet - even for unknown callers
  // Create a minimal lead object for unknown callers
  const callLead = leadData || {
    id: null,
    first_name: 'Unknown',
    last_name: 'Caller',
    phone: callRef.current?.parameters?.From || 'Unknown'
  };
  setCallEndedLead(callLead);
  setShowOutcomeSheet(true);
});
```

**Fix 3: CallOutcomeSheet Handle Unknown Leads**
Update CallOutcomeSheet to handle `lead.id = null` case:
- Skip sequence enrollment
- Show "Create Lead" button
- Still log to communications with `lead_id = null`

---

## Bug #3: Mobile Scroll Triggers Click (P1)

### Known Issue
Per LESSONS.md: "Touch scroll triggering click on mobile"

**File:** `admin/src/hooks/useLongPress.js`
- Already has scroll detection fix
- But components with CUSTOM touch handlers (like SwipeableLeadCard in Leads.jsx) need same pattern

### Fix Location
`admin/src/pages/Leads.jsx` - SwipeableLeadCard component

---

## Bug #4: Messages in DB But Not Showing in UI (Fitzgerald) (P1)

### Possible Causes

1. **Type mismatch** - Per LESSONS.md: "Type field conventions in communications table - Use `type: 'sms_outbound'` NOT `type: 'sms'`"
2. **Category mismatch** - Backend sends category frontend doesn't render
3. **Filter excluding** - Message may be in "handled" state and filtered out

### Investigation Needed

- [ ] Query DB for Fitzgerald's communications
- [ ] Check `type` field values
- [ ] Check if any filter is hiding messages
- [ ] Check frontend filter logic in Messages.jsx

---

## Summary

| Bug | Root Cause | Fix Effort |
|-----|-----------|-----------|
| #1 Inbound SMS | Silent failure - no error handling on lead update | 30 min |
| #2 Inbound Calls | Race condition + unknown callers skip outcome logging | 1 hr |
| #3 Mobile Scroll | Missing scroll detection in custom handler | 30 min |
| #4 Messages Missing | Type must be `sms_inbound` or `sms_outbound` exactly | 30 min |

---

## Ready for Builder

### Bug #1: Inbound SMS Not Updating Leads
**File:** `src/routes/webhooks.js`
**Fix:**
1. Add error handling to lead update (lines 289-293)
2. Add logging when lead not found
3. Consider Slack alert for failed updates

### Bug #2: Inbound Calls Not Logging
**Files:** `admin/src/components/IncomingCallHandler.jsx`
**Fix:**
1. Remove competing `setTimeout(resetCallState, 500)` from disconnect handler (line 234)
2. Always show CallOutcomeSheet for inbound calls (even unknown callers)
3. Create minimal lead object for unknown callers

### Bug #3: Mobile Scroll Triggers Click
**File:** `admin/src/pages/Leads.jsx` - SwipeableLeadCard
**Fix:**
- Add vertical scroll detection (>10px = scroll, not click)
- Pattern exists in `useLongPress.js` - replicate it

### Bug #4: Messages in DB Not Showing
**File:** Check communications table
**Investigation:**
- Query: `SELECT type, content FROM communications WHERE lead_id = [Fitzgerald's lead]`
- If `type` is NOT `sms_inbound` or `sms_outbound`, it won't show
- Fix: Ensure all SMS inserts use correct type values

---

## Investigation Complete

**Plan:** `docs/p0-bugs-investigation.md`
**Blockers:** None
**Ready for Builder:** Yes

---

*Investigation by: Architect*
*Date: Jan 29, 2026*
