feat: add usage tracking to errored jobs#216
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b781213. Configure here.
| } | ||
| if (failures.length > 0) { | ||
| throw failures[0]; | ||
| } |
There was a problem hiding this comment.
Embeddings catch drops usage
High Severity
When an embedding step fails, the inner catch replaces the original rejection with a new Error that never copies usage. The workflow wrapper only aggregates collectedUsage plus usage read from the thrown error, so tokens from the failing embed call (often the only provider call) never appear on the final error.
Reviewed by Cursor Bugbot for commit b781213. Configure here.
| } | ||
| } | ||
| if (failures.length > 0) { | ||
| throw failures[0]; |
There was a problem hiding this comment.
Embedding batch omits extra failures
Low Severity
If more than one embed in the same batch rejects, only the first failure is rethrown. Unlike collectSettledTranslationsOrRethrow in caption translation, usage on additional rejections in that batch is never folded into the error.
Reviewed by Cursor Bugbot for commit b781213. Configure here.


Overview
Attaches provider token usage to errors thrown by workflow functions. Today usage is only returned on the success path — when a workflow fails partway through (e.g. 9 of 10 translation chunks done, then one throws), every token burned before the failure is unreported. Now any workflow that throws after ≥1 provider call rethrows with
error.usagepopulated with the aggregate camelCaseTokenUsage(mirroring the AI SDK'sNoObjectGeneratedError.usageconvention, which the Robots consumer already reads). A throw before any provider call attaches nothing, and success-path return shapes are unchanged.What was changed
src/lib/token-usage.ts.aggregateTokenUsagemoved here from translate-captions (still re-exported there for back-compat), plus two new helpers:getErrorTokenUsage(error)duck-reads a plainusageobject off any error (AI SDK errors included, with fallback to v6's nestedinputTokenDetails/outputTokenDetails), andrethrowWithTokenUsage(error, collected)aggregates collected usage + the error's own usage and attaches it as a plain enumerableusageproperty (the kind that survives Workflow DevKit step serialization).wrapErrorforwards usage (src/lib/mux-ai-error.ts). It replaces the caught error with a newError, which was the one seam that silently dropped a failing call's usage. This single change carries usage through every existing rethrow site."use workflow"function became a thin wrapper owning acollectedUsagearray, delegating to a renamed*Internalfunction (the unchanged old body, which pushes usage after each provider call succeeds) and rethrowing viarethrowWithTokenUsage. This follows the pre-existinggenerateEmbeddingsInternalpattern and covers both failure windows: the LLM call itself failing, and a later step (S3 upload, Mux track creation, output validation) failing after tokens were burned. Applied togetSummaryAndTags,askQuestions,generateChapters,generateEngagementInsights,translateCaptions,editCaptions,hasBurnedInCaptions,generateEmbeddings/generateVideoEmbeddings.Promise.allfan-outs (batch loop intranslateCaptionTrack, split-retry recursion intranslateChunkWithFallback) now usePromise.allSettledvia a sharedcollectSettledTranslationsOrRethrowhelper, so a failed chunk's error carries usage from completed chunks, fulfilled siblings, other rejections, and the failed pre-split attempt.embed()usage captured. Previously discarded entirely; the per-chunk step now reports it (tokens→inputTokens/totalTokens) so the error path can attach it. Success-path result intentionally unchanged.getModerationScoresandtranslateAudiomake no token-bearing provider calls (free moderation endpoints; duration-priced ElevenLabs dubbing), so there is nothing to attach. The optionalerror.usageContextnice-to-have from the handoff was skipped.tests/unit/token-usage.test.ts(helpers +wrapErrorforwarding) andtests/unit/translate-captions-error-usage.test.ts(acceptance criteria end-to-end with a mocked model: mid-chunking failure carries the completed chunk's summed usage; pre-provider-call failure has nousageproperty).Note
Medium Risk
Touches error handling across many workflows and changes failure semantics for billing observability; behavior is additive and success paths are unchanged, but broad workflow coverage warrants careful regression testing.
Overview
Failed LLM workflows can now expose aggregate token usage on thrown errors via a plain
error.usagefield (same shape as successful results and AI SDK failures), so partial runs still show up in cost/COGS reporting.A new
src/lib/token-usage.tscentralizesaggregateTokenUsage(moved from translate-captions, still re-exported there),getErrorTokenUsage(readsusagefrom errors, including AI SDK v6 nested token details), andrethrowWithTokenUsage(merges collected + failing-call usage and rethrows).wrapErrornow copiesusageonto wrapped errors so it is not dropped at rethrow sites.LLM workflows (
askQuestions, chapters, summarization, engagement insights, burned-in captions, edit/translate captions, embeddings) collect usage after each provider call and use thin wrappers that callrethrowWithTokenUsageon failure. Success return types are unchanged; failures before any provider call still have nousage.Multi-call paths use
Promise.allSettledso completed work is counted: translate-captions batches/split retries viacollectSettledTranslationsOrRethrow, and embeddings batches record usage from fulfilled chunks before rethrowing. Embeddings steps now surfaceembed()token counts for the error path only.Unit tests cover the helpers,
wrapErrorforwarding, and translate-captions error-path usage.Reviewed by Cursor Bugbot for commit b781213. Bugbot is set up for automated code reviews on this repo. Configure here.