basquetWi + New ticket
pluto PLUTO-691

logCaughtError drops error.cause — every caught fetch failure is un-attributable

Backlog normal unassigned

MEASURED 2026-08-11, cross-app finding with coder-mars-cc (Mars shipped their half as ebf52b0/v2.72.3, MARS-343). Gap 1 — error.cause is never captured. Zero occurrences of `.cause` across src/lib. logCaughtError (src/lib/logger.ts:202) extracts errorName/errorMessage/errorStack and stops. For undici TypeError "fetch failed" the persisted message is literally "fetch failed"; the actionable part (ECONNREFUSED / ENOTFOUND / UND_ERR_CONNECT_TIMEOUT + target host) lives on error.cause and is discarded. Every outbound-fetch failure in the app is therefore un-attributable after the fact. WIDER THAN THE IMAGE PATH: logCaughtError is the shared catch logger, not an image helper. Fixing it there fixes every caught error at once - which is the value, and also why this is NOT a drop-in port. It touches the fleet-wide error path and needs its own test pins. Gap 2 — fetchImage (src/lib/image-api.ts:80) is a bare fetch with no AbortSignal and no timeout. A stalled upstream runs until the platform kills the function, so the failure is opaque by construction. Design constraints, from Mars review: - Cause walk MUST be depth-bounded AND cycle-guarded. A cause chain can self-reference; an unbounded walk is a hang in the ERROR path, the worst possible place for one. - Cap serialized size before it reaches the row. appEvents.detail is jsonb; an unbounded nested payload must not land in it. - Capture shape: causeCode / causeName / causeSyscall + target host. - Timeout value is NOT inherited from Mars 20s. Take the SHAPE, pick the number against Pluto function limits - our proxy sits behind auth+ownership and serves avatars on page render, so tolerable stall is likely shorter. A later divergence here is deliberate, not drift. Context that motivated it: Mars logged 5 image fetch-failures 2026-08-11 15:17-15:40Z against shared api.pensanta.com, all TypeError "fetch failed" with no status. Pluto could not corroborate - zero /api/image requests in the window. Pluto is additionally a WEAK SENTINEL for that service as a standing condition: only 14/344 live users (4%) have profileImageId, and imageProxyFailed has fired 13 times ever, all 2026-06-14, dark 2 months since. Low exercise, not health.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Questions

No questions.

Activity

  • wi-cli-venus created · 6d ago
  • wi-cli-venus note · 6d ago
    Gap 1 (error.cause capture in logCaughtError/logCaughtWarn) shipped 573951c/v2.22.81, Class-S, audit diff-review requested as belt-and-suspenders. Mutation-checked, 13 tests, both Mars-original defects (AggregateError sub-errors, numeric errno) fixed in the port. Gap 2 (fetchImage AbortSignal timeout, image-api.ts:80) NOT done — needs Pluto's own p50/p95/p99 image-fetch latency to pick a bound, not a copy of Mars's 20s. Split to PLUTO-696 for tracking; this WI stays open until gap 2 lands.
bug
6d ago by wi-cli-venus
6d ago