Skip to content

feat: add usage tracking to errored jobs#216

Open
walkerfrankenberg wants to merge 1 commit into
mainfrom
wf/errors
Open

feat: add usage tracking to errored jobs#216
walkerfrankenberg wants to merge 1 commit into
mainfrom
wf/errors

Conversation

@walkerfrankenberg

@walkerfrankenberg walkerfrankenberg commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

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.usage populated with the aggregate camelCase TokenUsage (mirroring the AI SDK's NoObjectGeneratedError.usage convention, which the Robots consumer already reads). A throw before any provider call attaches nothing, and success-path return shapes are unchanged.

What was changed

  • New src/lib/token-usage.ts. aggregateTokenUsage moved here from translate-captions (still re-exported there for back-compat), plus two new helpers: getErrorTokenUsage(error) duck-reads a plain usage object off any error (AI SDK errors included, with fallback to v6's nested inputTokenDetails/outputTokenDetails), and rethrowWithTokenUsage(error, collected) aggregates collected usage + the error's own usage and attaches it as a plain enumerable usage property (the kind that survives Workflow DevKit step serialization).
  • wrapError forwards usage (src/lib/mux-ai-error.ts). It replaces the caught error with a new Error, which was the one seam that silently dropped a failing call's usage. This single change carries usage through every existing rethrow site.
  • Every LLM workflow wrapped with a usage-collecting catch. Each exported "use workflow" function became a thin wrapper owning a collectedUsage array, delegating to a renamed *Internal function (the unchanged old body, which pushes usage after each provider call succeeds) and rethrowing via rethrowWithTokenUsage. This follows the pre-existing generateEmbeddingsInternal pattern 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 to getSummaryAndTags, askQuestions, generateChapters, generateEngagementInsights, translateCaptions, editCaptions, hasBurnedInCaptions, generateEmbeddings/generateVideoEmbeddings.
  • translate-captions: partial chunk usage survives failures. Both Promise.all fan-outs (batch loop in translateCaptionTrack, split-retry recursion in translateChunkWithFallback) now use Promise.allSettled via a shared collectSettledTranslationsOrRethrow helper, so a failed chunk's error carries usage from completed chunks, fulfilled siblings, other rejections, and the failed pre-split attempt.
  • embeddings: embed() usage captured. Previously discarded entirely; the per-chunk step now reports it (tokensinputTokens/totalTokens) so the error path can attach it. Success-path result intentionally unchanged.
  • Not touched: getModerationScores and translateAudio make no token-bearing provider calls (free moderation endpoints; duration-priced ElevenLabs dubbing), so there is nothing to attach. The optional error.usageContext nice-to-have from the handoff was skipped.
  • Tests: tests/unit/token-usage.test.ts (helpers + wrapError forwarding) and tests/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 no usage property).

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.usage field (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.ts centralizes aggregateTokenUsage (moved from translate-captions, still re-exported there), getErrorTokenUsage (reads usage from errors, including AI SDK v6 nested token details), and rethrowWithTokenUsage (merges collected + failing-call usage and rethrows). wrapError now copies usage onto 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 call rethrowWithTokenUsage on failure. Success return types are unchanged; failures before any provider call still have no usage.

Multi-call paths use Promise.allSettled so completed work is counted: translate-captions batches/split retries via collectSettledTranslationsOrRethrow, and embeddings batches record usage from fulfilled chunks before rethrowing. Embeddings steps now surface embed() token counts for the error path only.

Unit tests cover the helpers, wrapError forwarding, 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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-io

snyk-io Bot commented Jul 13, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b781213. Configure here.

}
}
if (failures.length > 0) {
throw failures[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b781213. Configure here.

@walkerfrankenberg walkerfrankenberg requested review from a team and monsieurBoutte July 14, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant