CedarCopilot wants to merge 1 commit into staging from fix/chris-compute-metric-observability
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: deal-metrics.ts.
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: conversationReasoningActions.ts.
Libra ran this behavior against the change and confirmed the check detects when it breaks.
Changed code: conversationReasoningActions.ts.
Live on prod, watching, 2 days left
Libra has verdicts on 0 of 5 tracked behaviors on prod; 5 are still being checked. Libra checks hourly for 3 days after each deploy.
Christopher Grittner (Admyral/HeyTelo) hung a chat turn on 2026-09-22 (thread thread-1790101387604-j1lemp6) asking for a pipeline KPI report. The agent fired several conversation-analytics compute-metric calls, then the whole generation went silent forever: no error frame, no done, the 20-minute abort timer never fired, and nothing was ever persisted , not even the user's own original message.
I initially suspected a missing statement_timeout on the raw SQL (the same class of bug fixed once before for getRecentExecutionsTool.ts). Checked postgres_logs and pg_stat_statements for the exact window: no evidence of a slow or hung query , that SQL shape has run exactly once in prod history, at 3.2s. So it isn't the DB, or at least isn't a DB query duration problem.
The real problem: computeMetric() and the compute-metric action in conversationReasoningActions.ts have zero logging anywhere , no entry log, no completion log, on either success or failure. Whatever actually stalled (a CPU-bound in-memory reduction, a stuck downstream round-trip, something else), there was never going to be a trace of it. That's why this incident is unresolved rather than merely unresolved-so-far.
conversationReasoningActions.ts , entry log right before calling computeMetric (run_id, user_id, thread_id, org_id, metric, group_by, org_wide), and an exit log on both the success path (row_count, duration_ms) and the error path (error, duration_ms). This is the tool boundary; org_wide directly answers "was this an unscoped org-wide scan."deal-metrics.ts , an entry log at computeMetric's own boundary (metric, group_by, conversation_ids_count), plus a scope-sizing log specifically inside the aop_conversion branch , the one metric that chains a scope query, a change-history query, an AOP-resolution query, and an O(scope × history-per-conversation) in-memory reduction. It's the likeliest of the 9 metrics to stall on an org with heavy AOP churn, so it gets its own instrumentation rather than sharing the generic entry log's granularity.No behavior change, logging only. The next occurrence of this symptom should leave an actual trail: either the entry log fires with nothing after it (pins the hang inside this boundary, and which metric), or it doesn't fire at all (points further upstream, at the SDK↔MCP dispatch layer instead).
The statement_timeout/abortSignal wiring I originally proposed is NOT in this PR , it's unvalidated against this specific incident (see above) and shouldn't ship as if it were the fix. This PR is the prerequisite: get visibility first, then fix what the trace actually shows.
pnpm --filter @zero/server run types , cleanpnpm --filter @zero/server exec vitest run src/services/crm/__tests__/deal-metrics.test.ts src/mastra/tools/conversation/__tests__/conversationReasoningActions.test.ts , 31/31 pass[compute-metric] / [computeMetric] log lines in Axiom on a recurrence🤖 Generated with Claude Code
The PR appears safe to merge, with two non-blocking observability refinements recommended so future incident traces are accurately scoped and consistently correlated.
Libra has not measured any production surfaces for this change yet.