Skip to content

fix: batch resolve 5 PostHog production errors (chat error handling)#344

Open
JeffOtano wants to merge 5 commits intomainfrom
fix/posthog-batch-2026-05-06
Open

fix: batch resolve 5 PostHog production errors (chat error handling)#344
JeffOtano wants to merge 5 commits intomainfrom
fix/posthog-batch-2026-05-06

Conversation

@JeffOtano
Copy link
Copy Markdown
Owner

@JeffOtano JeffOtano commented May 6, 2026

Summary

Batch fix for PostHog production errors. Two independent fixes share this PR because both target the chat error path:

  1. Convex (stripOrphanedToolCalls adjacency repair) — addresses PostHog #019d510a (158 occurrences, 58 affected users), the highest-impact unresolved issue. Gemini was rejecting our message history with "Please ensure that function call turn comes immediately after a user turn or after a function response turn." The existing strip pass proved a tool-result existed somewhere in history but didn't enforce that it sat in the slot Gemini requires.

  2. Next.js (welcome-page BYOK error classification) — addresses a cluster of issues (#019d7fcc 50 occ, #019dea0c 8 occ, #019d96f1 2 occ, etc.) where errors thrown by api.chat.createThreadWithMessage (rate-limit + BYOK provider failures) leaked as uncaught exceptions on /chat because the welcome page didn't route them through the existing parseByokErrorFailureBanner pattern that ChatInput already uses.

Fixes

PostHog issue File Occurrences Users Fix summary Specialist
019d510a convex/ai/contextWindow.ts, convex/ai/toolCallAdjacency.ts 158 58 New adjacency-repair pass enforces Gemini's function-call/response immediate-pairing rule. Drops unpaired tool-calls and stray tool messages. Logs a console.warn with drop count for observability. convex-specialist
019d7fcc src/app/(app)/chat/page.tsx, src/app/(app)/chat/WelcomeInput.tsx 50 20 Classify BYOK errors via existing parseByokError; render FailureBanner instead of generic string. nextjs-specialist
019dea0c (same) 8 1 Same fix — "prepayment credits depleted" now hits the BYOK quota banner. nextjs-specialist
019d96f1 (same) 2 1 Same fix — Anthropic credit-balance error hits BYOK quota banner. nextjs-specialist
019dc294 (same) 3 1 Same fix — generic "Server Error" from createThreadWithMessage now rendered through the welcome error UI. nextjs-specialist

Test plan

  • convex/ai/toolCallAdjacency.test.ts — 7 new regression tests covering: text-only follower, fresh-user follower, matched tool-result, mismatched tool-result id, live-approval flow, leading-tool-message, two consecutive tool messages.
  • convex/ai/stripOrphanedToolCalls.test.ts — 12 existing tests still pass (set-based logic unchanged).
  • convex/ai/contextWindow.test.ts — 22 existing tests pass.
  • Full vitest suite — 1673 passed / 13 skipped (no regressions).
  • npx tsc --noEmit — clean.
  • npm run lint — clean.
  • No new dependencies, no schema changes.

Skipped

PostHog has many active error-tracking issues whose root cause is outside our code; left active in PostHog (operator action only):

  • Browser extension / page noise (~8 issues): Firefox iOS reader-mode (window.__firefox__.reader), Chrome runtime.sendMessage, Script error. from cross-origin scripts, n.standardSelectors, ResizeObserver loop completed. Not our code.
  • Transient user-network errors (~6 issues): NetworkError when attempting to fetch resource (chatty-hawk-29.convex.cloud), Failed to fetch, Load failed. Expected churn; not actionable in code.
  • Convex deploy stale-tab (ChunkLoadError, 2 issues): low priority; could be improved with a stale-tab refresh prompt as a follow-up.
  • Pure provider quota / overload errors (~10 issues with very low occurrence after the Next.js fix lands): once routed through the BYOK banner most of these stop being uncaught exceptions. Any residual ones are upstream provider issues outside our control.

Deferred — needs human input

None.

Dropped after failed verification

None.

Observability

The new adjacency repair pass emits console.warn with a drop count when it triggers. Watch Convex logs for [stripOrphanedToolCalls] adjacency repair dropped N message(s)/part(s) — sustained drops there indicate a regression in upstream message persistence (the bug class this pass defends against).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Send workout plans directly to Garmin devices with scheduled delivery
    • Improved error handling for API key issues in chat
    • Track Garmin workout delivery status and history
    • Enhanced AI conversation message repair for better reliability
  • Tests

    • Added comprehensive test coverage for Garmin workout integration
    • Added validation tests for AI message handling
  • Other

    • Account deletion and data export now include Garmin workout data
    • Linked schedule days to their corresponding workout plans

JeffOtano and others added 5 commits May 4, 2026 22:18
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
…i (PostHog #019d510a)

Gemini rejects message history where an assistant function-call turn isn't
immediately followed by a function-response turn. The set-based logic in
stripOrphanedToolCalls proved a tool-result existed *somewhere* in history
but didn't enforce that it sat in the slot Gemini requires, causing 158
exceptions across 58 users in production.

Add a belt-and-suspenders adjacency repair pass that walks the surviving
messages and drops any unpaired tool-call (or stray tool message). Live
approval state is preserved (the runtime appends the approval-response
before the next turn). The pass returns a drop count which is logged
through console.warn so a regression in upstream persistence is observable
in Convex logs.

- Add convex/ai/toolCallAdjacency.ts with the repair logic and a drop
  counter.
- Wire it into stripOrphanedToolCalls in convex/ai/contextWindow.ts.
- Add 7 regression tests in convex/ai/toolCallAdjacency.test.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… #019dea0c, #019d96f1)

The /chat welcome page calls api.chat.createThreadWithMessage but didn't
route the thrown errors through the existing parseByokError + FailureBanner
pattern that ChatInput already uses. Result: rate-limit and BYOK provider
errors (Gemini quota, Anthropic credit, prepayment depleted) leaked as
uncaught exceptions and showed users a generic 'Could not send your
message' instead of an actionable banner.

- src/app/(app)/chat/page.tsx: add byokError state; classify errors in
  both the auto-send useEffect and suggestion-button .catch; render
  FailureBanner with priority over the generic <p>.
- src/app/(app)/chat/WelcomeInput.tsx: same treatment for manually-typed
  welcome submissions; add console.error so unclassified failures leave a
  debuggable trail.

Mirrors the established pattern in src/features/chat/ChatInput.tsx.
No new dependencies, no refactor of unrelated code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90001008-ed7e-4cdf-bdde-3aa68903b2de

📥 Commits

Reviewing files that changed from the base of the PR and between eab9305 and 7a50707.

⛔ Files ignored due to path filters (1)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (22)
  • convex/accountDeletion.test.ts
  • convex/accountDeletion.ts
  • convex/ai/contextWindow.ts
  • convex/ai/toolCallAdjacency.test.ts
  • convex/ai/toolCallAdjacency.ts
  • convex/dataExport.ts
  • convex/garmin/trainingApi.test.ts
  • convex/garmin/trainingApi.ts
  • convex/garmin/workoutDelivery.test.ts
  • convex/garmin/workoutDelivery.ts
  • convex/garmin/workoutPayload.ts
  • convex/migrations/repairOrphanedAuthAccounts.ts
  • convex/rateLimits.ts
  • convex/schedule.ts
  • convex/schema.ts
  • convex/userData.test.ts
  • convex/userData.ts
  • src/app/(app)/chat/WelcomeInput.tsx
  • src/app/(app)/chat/page.tsx
  • src/app/(app)/schedule/[dayIndex]/page.tsx
  • src/features/schedule/GarminWorkoutDeliveryCard.test.tsx
  • src/features/schedule/GarminWorkoutDeliveryCard.tsx
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/posthog-batch-2026-05-06

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

This pull request introduces Garmin workout delivery functionality enabling users to send Roni training plans to Garmin devices, adds adjacency repair for orphaned tool-calls in AI conversations, implements bring-your-own-key error handling in chat, and integrates account deletion support for the new Garmin delivery table.

Changes

Garmin Workout Delivery Feature

Layer / File(s) Summary
Schema & Registration
convex/schema.ts, convex/userData.ts
garminWorkoutDeliveries table added with userId, workoutPlanId, scheduledDate, status, Garmin IDs, and timestamps; indexed by userId, workoutPlanId, and composite key. Registered in USER_DATA_TABLES for batch deletion and JSON export.
Workout Payload Building
convex/garmin/workoutPayload.ts, convex/garmin/workoutPayload.test.ts
Constants, types, and functions to map Tonal exercises to Garmin categories, expand blocks into steps (including rest), resolve durations, and assemble complete Garmin Strength workout payloads from plans. Tests verify category mapping, block expansion, and step properties.
Garmin API Integration
convex/garmin/trainingApi.ts, convex/garmin/trainingApi.test.ts
OAuth1-signed request utilities, JSON parsing, remote ID extraction, and public function createAndScheduleGarminWorkout that creates a workout and schedules it with failure cleanup. Tests mock fetch to verify request bodies and returned IDs.
Delivery State Management
convex/garmin/workoutDelivery.ts, convex/garmin/workoutDelivery.test.ts
Query getMyWorkoutDelivery, internal mutations startDeliveryAttempt, markDeliverySent, markDeliveryFailed, and action sendWorkoutPlanToGarmin that orchestrate validation, rate limiting, credential decryption, payload construction, and Garmin API calls. Tests cover claiming, deduplication, and delivery state transitions.
Data Export Integration
convex/dataExport.ts, convex/userData.test.ts
Type GarminWorkoutDeliveryExportRow added; ExportedData interface extended. Functions fetch deliveries by userId and map to export rows (stripping internal metadata). Test verifies Garmin deliveries export without Convex IDs.
Rate Limiting
convex/rateLimits.ts
New bucket sendGarminWorkout added: 12 per hour, capacity 6, using token bucket model.
Schedule Data Model
convex/schedule.ts
ScheduleDay interface extended with optional workoutPlanId?: Id<"workoutPlans"> field; populated when constructing enriched schedule days.
UI Integration & Rendering
src/app/(app)/schedule/[dayIndex]/page.tsx, src/features/schedule/GarminWorkoutDeliveryCard.tsx, src/features/schedule/GarminWorkoutDeliveryCard.test.tsx
New client component GarminWorkoutDeliveryCard fetches delivery and connection status, checks permissions, handles send action with loading/error states, and displays status badge and connect prompt. Rendered conditionally in schedule day page when workout plan exists. Component tests cover send flow, permission checks, connectivity, error handling, and past-date disabling.

Account Deletion & User Data Integration

Layer / File(s) Summary
Batch Deletion Logic
convex/accountDeletion.ts, convex/accountDeletion.test.ts
Case added to takeBatchForDeletion switch to handle "garminWorkoutDeliveries" table using by_userId index and BATCH_SIZE. Test verifies deletion of target user's rows while preserving other users' data.
User Data Registry & Safety
convex/userData.ts, convex/migrations/repairOrphanedAuthAccounts.ts
garminWorkoutDeliveries registered in USER_DATA_TABLES with batch-delete and JSON-export keys. Safety index mapping garminWorkoutDeliveries: "by_userId" added to USER_TABLE_BATCH_SAFETY_INDEXES for orphaned-account repairs.

AI Tool-Call Adjacency Repair

Layer / File(s) Summary
Adjacency Repair Logic
convex/ai/toolCallAdjacency.ts
New module implementing two-pass repair: repairAssistantToolCalls prunes or preserves tool-call parts based on next-message adjacency and live-approval state; repairOrphanToolMessages removes orphaned tool messages. Public API enforceToolCallAdjacency runs both passes and returns repaired messages with drop count for observability.
Integration into Context Window
convex/ai/contextWindow.ts
stripOrphanedToolCalls now applies two-pass pipeline: first maps/filters messages to normalize, then calls enforceToolCallAdjacency to repair adjacency violations; logs dropped-message count.
Test Coverage
convex/ai/toolCallAdjacency.test.ts
New test suite "stripOrphanedToolCalls — adjacency repair" exercises scenarios: dropping tool-calls after text-only assistants, preserving paired tool-results, handling live-approval flows, removing orphaned tool messages, and mismatched ID pairs.

Chat Bring-Your-Own-Key Error Handling

Layer / File(s) Summary
Error State & UI
src/app/(app)/chat/WelcomeInput.tsx, src/app/(app)/chat/page.tsx
BYOK error state added to both components. Error catch paths parse BYOK failures via parseByokError; if present, set byokError state, otherwise fall back to generic error. Render logic prioritizes FailureBanner when byokError exists. Success paths reset BYOK error state.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as Schedule Day Page
    participant Card as GarminWorkoutDeliveryCard
    participant Query as Convex Query
    participant Action as sendWorkoutPlanToGarmin Action
    participant Validate as Validation & Auth
    participant RateLimit as Rate Limiter
    participant PayloadBuilder as Payload Builder
    participant GarminAPI as Garmin Training API
    participant DB as Database
    
    User->>UI: View schedule day with workout
    UI->>Card: Render card (workoutPlanId, scheduledDate)
    Card->>Query: Fetch delivery status & connection
    Query-->>Card: Return delivery & connection state
    Card-->>UI: Display status (unsent/sent/failed) & button
    
    User->>Card: Click "Send to Garmin"
    Card->>Card: Set loading=true
    Card->>Action: sendWorkoutPlanToGarmin(workoutPlanId, scheduledDate)
    
    Action->>Validate: Validate date format & user permissions
    Validate-->>Action: ✓ Authorized
    Action->>RateLimit: Check rate limit (sendGarminWorkout)
    RateLimit-->>Action: ✓ Within quota
    
    Action->>DB: startDeliveryAttempt(userId, workoutPlanId, scheduledDate)
    DB-->>Action: deliveryId in 'sending' state
    
    Action->>Action: Decrypt Garmin credentials
    Action->>Action: Fetch workout plan & movements
    Action->>PayloadBuilder: buildGarminStrengthWorkoutPayloadFromPlan(plan)
    PayloadBuilder-->>Action: GarminWorkoutPayload
    
    Action->>GarminAPI: createAndScheduleGarminWorkout(credentials, payload, date)
    GarminAPI-->>Action: {garminWorkoutId, garminScheduleId}
    
    Action->>DB: markDeliverySent(deliveryId, garminIds)
    DB-->>Action: Updated delivery (status='sent')
    
    Action-->>Card: SendGarminWorkoutResult (success)
    Card->>Card: Set loading=false
    Card->>Query: Refetch delivery status
    Query-->>Card: Return sent delivery
    Card-->>UI: Display "Sent to Garmin" + timestamp
    Card-->>User: ✓ Workout scheduled on Garmin device
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • JeffOtano/roni#289: Modifies the same AI adjacency repair logic in contextWindow.ts and introduces enforceToolCallAdjacency updates.
  • JeffOtano/roni#324: Extends account-deletion batch handler (takeBatchForDeletion) to support new user-scoped table deletions.
  • JeffOtano/roni#340: Adds garminWorkoutDeliveries table and related deletion/export/delivery flow integration.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: batch resolve 5 PostHog production errors (chat error handling)' directly describes the main objective of the PR, which is to fix multiple PostHog production errors in chat error handling.
Description check ✅ Passed The PR description comprehensively covers the required template sections: a clear Summary explaining the two independent fixes, a Changes section with organized File/Objective details, a detailed Test plan with specific test coverage, and explicit commit message summaries.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/posthog-batch-2026-05-06
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/posthog-batch-2026-05-06

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant