{"record":{"id":"b98fff6bf0388ec2","repo":"JuliusBrussee/caveman","slug":"cave-mastra-usage-missing","errorCode":"cave_mastra_usage_missing","errorMessage":"cave_mastra_usage_missing","messagePattern":"cave_mastra_usage_missing","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/adapters.ts","lineNumber":407,"sourceCode":"    throw new Error(\"cave_vercel_usage_missing\");\n  }\n  const normalized = normalizeUsage({\n    inputTokens: noCache,\n    outputTokens: usage.outputTokens,\n    cacheReadTokens: cacheRead,\n    cacheWriteTokens: cacheWrite,\n    reasoningTokens: output.reasoningTokens,\n    totalTokens: usage.totalTokens,\n  }, reasoningRequired, \"cave_vercel_usage_missing\");\n  return normalized;\n}\n\nfunction usageFromMastra(value: unknown, reasoningRequired: boolean): NormalizedUsage {\n  const usage = record(value, \"cave_mastra_usage_missing\");\n  const totalInput = strictInteger(usage.inputTokens, \"cave_mastra_usage_missing\");\n  const cacheRead = optionalInteger(usage.cachedInputTokens, \"cave_mastra_usage_missing\");\n  const cacheWrite = optionalInteger(usage.cacheCreationInputTokens, \"cave_mastra_usage_missing\");\n  if (cacheRead + cacheWrite > totalInput) throw new Error(\"cave_mastra_usage_missing\");\n  return normalizeUsage({\n    inputTokens: totalInput - cacheRead - cacheWrite,\n    outputTokens: usage.outputTokens,\n    cacheReadTokens: cacheRead,\n    cacheWriteTokens: cacheWrite,\n    reasoningTokens: usage.reasoningTokens,\n    totalTokens: usage.totalTokens,\n  }, reasoningRequired, \"cave_mastra_usage_missing\");\n}\n\nfunction usageFromEveEvents(events: unknown[], reasoningRequired: boolean): NormalizedUsage {\n  let totalInputTokens = 0;\n  let outputTokens = 0;\n  let cacheReadTokens = 0;\n  let cacheWriteTokens = 0;\n  let steps = 0;\n  for (const event of events) {\n    if (!isRecord(event) || event.type !== \"step.completed\" || !isRecord(event.data)) continue;","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/adapters.ts#L389-L425","documentation":"Returned by ValidateTenantSchema when pool.BeginTx with RepeatableRead + ReadOnly fails before the schema inspection can run. This is a connection/session-level failure (pool exhausted, connection dropped, server refusing read-only transactions), not a schema problem. The validation is designed to run at startup to prove tenant-isolation invariants; failing to even open its transaction means the database side is unhealthy or the pool is misconfigured.","triggerScenarios":"Calling ValidateTenantSchema when the pool cannot hand out a connection (max conns reached), the server rejects READ ONLY REPEATABLE READ (rare, e.g. hot-standby restrictions), or the context is already cancelled/expired at call time.","commonSituations":"Running schema validation concurrently with migrations that hold locks; calling it with a short startup timeout against a cold database; pool_max_conns too small for the number of startup validation goroutines; standby replicas in recovery that reject some transaction modes.","solutions":["Check the wrapped error: 'context deadline exceeded' -> enlarge the startup/health timeout; 'pool exhausted' -> raise pool_max_conns or serialize startup probes","Ensure the database accepts READ ONLY REPEATABLE READ transactions where validation runs (primary, not a restricted standby)","Run validation once at startup and retry with backoff on transient connection errors rather than failing the deploy immediately","If a migration is in flight, order startup so validation runs after migrations complete"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 2*time.Second)\nerr := postgresconfig.ValidateTenantSchema(ctx, pool) // may hit deadline on cold start\n\n// after\nerr := retry.Do(func() error {\n    vctx, vcancel := context.WithTimeout(ctx, 30*time.Second)\n    defer vcancel()\n    return postgresconfig.ValidateTenantSchema(vctx, pool)\n}, retry.WithDelay(2*time.Second), retry.WithAttempts(5))","handlingStrategy":"retry","validationCode":"func poolCanBegin(ctx context.Context, pool *pgxpool.Pool) error {\n    tx, err := pool.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.RepeatableRead, AccessMode: pgx.ReadOnly})\n    if err != nil {\n        return fmt.Errorf(\"cannot open read-only repeatable-read transaction (pool/server state): %w\", err)\n    }\n    return tx.Rollback(ctx)\n}","typeGuard":"func isBeginInspectionErr(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"postgres: begin tenant schema inspection\")\n}\n\nfunc isPoolExhausted(err error) bool {\n    return err != nil && (strings.Contains(err.Error(), \"pool\") && strings.Contains(err.Error(), \"exhausted\"))\n}","tryCatchPattern":"err := postgresconfig.ValidateTenantSchema(ctx, pool)\nif isBeginInspectionErr(err) {\n    if isPoolExhausted(errors.Unwrap(err)) {\n        // raise pool_max_conns or serialize startup probes, then retry\n    }\n    // transient connection failure: retry with backoff\n    err = retryCall(ctx, 3, 2*time.Second, func() error {\n        return postgresconfig.ValidateTenantSchema(ctx, pool)\n    })\n}\nif err != nil { return err }","preventionTips":["Run schema validation once at startup, ordered after migrations and before serving traffic","Size pool_max_conns for startup concurrency (migrations + validation + warmup) not just steady-state","Use a generous startup timeout for validation against cold or remote databases","Retry transient BeginTx failures with backoff instead of failing the deploy on the first attempt"],"tags":["postgres","transactions","pool","startup","schema-validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}