Back to skills
SKILL.md
1570 Gotchas 4c1f6f1b
ASecurity**Cause:** First STUN delayed during consensus forming (normal behavior) **Solution:** Subsequent connections are faster. CF detects DTLS ClientHello early to compensate.
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added October 11, 2026
Works with
Security analysis
100/100npx -y skills add tools-only/X-Skills --skill 1570-gotchas_4c1f6f1b --agent claude-codeAre you the author of 1570 Gotchas 4c1f6f1b?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-1570-gotchas-4c1f6f1b)# Gotchas & Troubleshooting
## Common Errors
### "Slow initial connect (~1.8s)"
**Cause:** First STUN delayed during consensus forming (normal behavior)
**Solution:** Subsequent connections are faster. CF detects DTLS ClientHello early to compensate.
### "No media flow"
**Cause:** SDP exchange incomplete, connection not established, tracks not added before offer, browser permissions missing
**Solution:**
1. Verify SDP exchange complete
2. Check `pc.connectionState === 'connected'`
3. Ensure tracks added before creating offer
4. Confirm browser permissions granted
5. Use `chrome://webrtc-internals` for debugging
### "Track not receiving"
**Cause:** Track not published, track ID not shared, session IDs mismatch, `pc.ontrack` not set, renegotiation needed
**Solution:**
1. Verify track published successfully
2. Confirm track ID shared between peers
3. Check session IDs match
4. Set `pc.ontrack` handler before answer
5. Trigger renegotiation if needed
### "ICE connection failed"
**Cause:** Network changed, firewall blocked UDP, TURN needed, transient network issue
**Solution:**
```typescript
pc.oniceconnectionstatechange = async () => {
if (pc.iceConnectionState === 'failed') {
console.warn('ICE failed, attempting restart')
await pc.restartIce() // Triggers new ICE gathering
// Create new offer with ICE restart flag
const offer = await pc.createOffer({ iceRestart: true })
await pc.setLocalDescription(offer)
// Send to backend → Cloudflare API
await fetch(`/api/sessions/${sessionId}/renegotiate`, {
method: 'PUT',
body: JSON.stringify({ sdp: offer.sdp }),
})
}
}
```
### "Track stuck/frozen"
**Cause:** Sender paused track, network congestion, codec mismatch, mobile browser backgrounded
**Solution:**
1. Check `track.enabled` and `track.readyState === 'live'`
2. Verify sender active: `pc.getSenders().find(s => s.track === track)`
3. Check stats for packet loss/jitter (see patterns.md)
4. On mobile: Re-acquire tracks when app foregrounded
5. Test with different codecs if persistent
### "Network change disconnects call"
**Cause:** Mobile switching WiFi↔cellular, laptop changing networks
**Solution:**
```typescript
// Listen for network changes
if ('connection' in navigator) {
;(navigator as any).connection.addEventListener('change', async () => {
console.log('Network changed')
await pc.restartIce() // Use ICE restart pattern above
})
}
// Or use PartyTracks (handles automatically)
```
## Retry with Exponential Backoff
```typescript
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const res = await fetch(url, options)
if (res.ok) return res
if (res.status >= 500) throw new Error('Server error')
return res // Client error, don't retry
} catch (err) {
if (i === maxRetries - 1) throw err
const delay = Math.min(1000 * 2 ** i, 10000) // Cap at 10s
await new Promise((resolve) => setTimeout(resolve, delay))
}
}
}
```
## Debugging with chrome://webrtc-internals
1. Open `chrome://webrtc-internals` in Chrome/Edge
2. Find your PeerConnection in the list
3. Check **Stats graphs** for packet loss, jitter, bandwidth
4. Check **ICE candidate pairs**: Look for `succeeded` state, relay vs host candidates
5. Check **getStats**: Raw metrics for inbound/outbound RTP
6. Look for errors in **Event log**: `iceConnectionState`, `connectionState` changes
7. Export data with "Download the PeerConnection updates and stats data" button
8. Common issues visible here: ICE failures, high packet loss, bitrate drops
## Limits
| Resource/Limit | Value | Notes |
| ------------------ | -------------- | --------------------------------------------------- |
| Egress (Free) | 1TB/month | Per account |
| Egress (Paid) | $0.05/GB | After free tier |
| Inbound traffic | Free | All plans |
| TURN service | Free | Included with SFU |
| Participants | No hard limit | Client bandwidth/CPU bound (typically 10-50 tracks) |
| Tracks per session | No hard limit | Client resources limited |
| Session duration | No hard limit | Production calls run for hours |
| WebRTC ports | UDP 1024-65535 | Outbound only, required for media |
| API rate limit | 600 req/min | Per app, burst allowed |
## Security Checklist
- ✅ **Never expose** `CALLS_APP_SECRET` to client
- ✅ **Validate user identity** in backend before creating sessions
- ✅ **Implement auth tokens** for session access (JWT in custom header)
- ✅ **Rate limit** session creation endpoints
- ✅ **Expire sessions** server-side after inactivity
- ✅ **Validate track IDs** before subscribing (prevent unauthorized access)
- ✅ **Use HTTPS** for all signaling (API calls)
- ✅ **Enable DTLS-SRTP** (automatic with Cloudflare, encrypts media)
- ⚠️ **Consider E2EE** for sensitive content (implement client-side with Insertable Streams API)
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.