Dave reply cutoff — 2026-09-13
Confirmed incident
Two Chinese Adventure pot reactions at 05:55 UTC failed with HTTP 422 invalid_reply / response_not_json. PostHog request IDs were correlated with the database usage rows and OpenRouter generation metadata. Both Gemini 3.8 Flash generations ended with length / MAX_TOKENS: 796 completion tokens, including 765 or 767 reasoning tokens. Dave's 800-token output cap left almost no room for the final JSON.
Both attempts already had durable pvz-dave-unheard usage rows and no matching wallet ledger debits. The failure was generation configuration, not a lost save or the pot interaction being rejected. An older Gemini Flash Preview incident also ended at the token limit; another older malformed response ended normally and its exact content was not available, so this does not attribute every historical invalid reply to truncation.
Final change: inherit the player's Yumina settings
- The first mitigation forced minimal reasoning and 2,048 output tokens. That override has been replaced: every Dave request reads the authenticated account's saved
preferences.aiConfig. Output/context length, reasoning, temperature, sampling penalties and streaming use the user's settings. - Shared generation validation/defaults now also supply the normal chat store. Missing or invalid individual preferences use those same defaults; valid preferences survive unrelated legacy fields. Provider/model selection still uses Yumina's existing account/model selection and billing intent checks.
- Wallet preflight uses the selected output and reasoning allowance. Settlement still uses actual provider usage; incomplete, silent, or uncommitted replies remain uncharged. Game-specific JSON and effect validation remain mandatory.
- Terminal token-limit responses report
response_truncated. We retain strict JSON and offer validation and one upstream attempt. An insufficient context setting is rejected before generation withcontext_too_small. - The former 20-second server and 24-second browser deadlines are replaced by the normal 600-second LLM request ceiling and a five-second client margin. SSE heartbeats keep the proxy connection active during reasoning or a nonstreaming provider call. Only the fully validated, transactionally saved reply is delivered. Leaving the conversation still cancels generation.
- Dave's concurrency slot lasts through that deadline. Other callers keep their existing concurrency TTL. Older clients can still receive JSON.
- PostHog result events include provider stop reason, completion tokens, and reasoning tokens, without dialogue or notebook contents.
The client retry for a failed reaction calls send(..., 'interaction') again; it does not repeat the native pot hit or gift deduction. This update changes the Dave browser shell and main API. It keeps the published native engine cbd111d5bb, save schema and multiplayer services unchanged.
Verification
The final branch passes pnpm build, pnpm typecheck, 74 server checks (game-NPC, billing coverage, rate limits and concurrency duration), 14 PvZ browser-shell checks and 16 app config-sync/model-handoff checks. Accounting tests exercise both the legacy JSON and SSE routes against a real PGlite transaction, including a truncated response that creates usage but no debit. Streaming checks cover heartbeats, cancellation, chunked Unicode and errors.
Real provider checks used the complete Dave prompt with synthetic Level 1-1 state and an authored introduction. The first two use the affected account's actual saved settings: 13,312 output tokens, high reasoning and temperature 0.7. The latter two use synthetic 16,000-token/high-reasoning/nonstreaming settings without changing the account:
| Model | Scenario | Result | Service time |
|---|---|---|---|
| Gemini 3.8 Flash | Chinese pot reaction | Valid reply, normal stop | 5.52 s |
| Gemini 3.8 Flash | English conversation | Valid reply, normal stop | 4.30 s |
| Gemini 3.8 Flash | Spanish gift reaction | Valid reply, normal stop | 8.97 s |
| Claude Sonnet 4.6 | Chinese pot reaction | Valid reply, leaves after pot hit | 32.95 s |
Each diagnostic generation was recorded as unheard platform usage: four usage rows, zero wallet debits, and no changes to a player's campaign or conversation. These are bounded provider checks, not a guarantee against future upstream outages. Regression tests also simulate a cutoff after hidden reasoning and verify no reply or debit is committed while the full provider usage is retained.
