Skip to content

feat: structured task errors across all SDKs#413

Merged
pratyush618 merged 8 commits into
masterfrom
feat/structured-task-error
Jul 12, 2026
Merged

feat: structured task errors across all SDKs#413
pratyush618 merged 8 commits into
masterfrom
feat/structured-task-error

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Task failures are now stored as a canonical JSON object — {errtype, message, traceback} — so any SDK and the dashboard can show the exception class and message without parsing language-specific traceback text
  • The storage layer stays format-agnostic: readers fall back to the raw string for legacy and core-generated system errors (timeouts, worker-death recovery, expiry, cancellation)
  • Retry filtering is unchanged: it matches on the live exception object before formatting, so the stored string never drives retry decisions

Changes

  • Binding contract: new "Task errors" section defining the canonical JSON, the fallback rule, and a shared test vector
  • Python: taskito/task_errors.py single encoder/decoder (the Rust worker paths call it, collapsing four duplicated format sites into one); outcome exceptions gain errtype, traceback, job_id, and raw_error; the error-summary helper is structure-aware
  • Node: task-error.ts encode/decode; the worker rethrows a JSON-carrying error and the dispatcher reads its reason so the stored string is the bare JSON; JobFailedError exposes errtype/traceback
  • Java: errors/TaskErrors codec (fully-qualified class name, stack frames) emitted by the worker bridge; JNI path unchanged
  • Dashboard: parse-or-fallback rendering across the job, dead-letter, and workflow error views, plus structure-aware dead-letter grouping

Test plan

  • Shared contract test vector asserted in every SDK's formatter
  • Python: 16 structured-error tests incl. end-to-end propagation; full suite green
  • Node: full suite green (encode/decode + real-worker end-to-end)
  • Java: codec + bridge tests; full gradle build green
  • Dashboard: parser + rendering tests green

Summary by CodeRabbit

  • New Features

    • Added consistent structured task errors across Python, Node.js, Java, and Rust integrations, including error type, message, and traceback details.
    • Exposed task-error encoding and decoding utilities in supported SDKs.
    • Dashboard error displays now show concise summaries with expandable traceback details and tooltips.
  • Bug Fixes

    • Preserved readable handling for legacy or unstructured error messages.
    • Improved failure reporting when a task rejection includes a specific reason.
  • Tests

    • Added coverage for structured error formatting, decoding, storage, display, and end-to-end failure propagation.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6147d5e5-964b-4789-ad75-a452526eb05a

📥 Commits

Reviewing files that changed from the base of the PR and between 3abfd38 and 62cfc1b.

📒 Files selected for processing (5)
  • sdks/java/src/main/java/org/byteveda/taskito/errors/TaskError.java
  • sdks/java/src/main/java/org/byteveda/taskito/errors/TaskErrors.java
  • sdks/java/src/test/java/org/byteveda/taskito/errors/TaskErrorsTest.java
  • sdks/node/src/task-error.ts
  • sdks/python/taskito/exceptions.py
📝 Walkthrough

Walkthrough

Adds a canonical JSON format for task failures, implements encoding and decoding across Python, Node, and Java SDKs, preserves structured details in raised errors, and updates dashboard error rendering with structured summaries and tracebacks.

Changes

Structured task-error contract

Layer / File(s) Summary
Contract and runtime formatting
crates/taskito-core/..., crates/taskito-node/..., crates/taskito-python/...
Documents the canonical error shape and updates worker failure paths to preserve structured error payloads with legacy fallbacks.
Python SDK propagation
sdks/python/taskito/..., sdks/python/tests/core/test_task_errors.py
Adds task-error codecs, structured failure details on terminal exceptions, worker integration, and end-to-end tests.
Node SDK propagation
sdks/node/src/..., sdks/node/test/core/task-error.test.ts
Adds task-error codecs, structured worker failure encoding, enriched JobFailedError fields, public exports, and propagation tests.
Java SDK propagation
sdks/java/src/main/..., sdks/java/src/test/...
Adds TaskError and TaskErrors, encodes worker failures, summarizes Sentry messages, and validates dead-letter decoding.
Dashboard parsing and presentation
dashboard/src/lib/..., dashboard/src/components/..., dashboard/src/features/..., dashboard/src/routes/...
Adds structured error parsing, summaries, tooltips, traceback blocks, and structured rendering across job, workflow, and dead-letter views with tests.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Worker
  participant Storage
  participant SDKResult
  participant Dashboard
  Worker->>Storage: persist canonical JSON task error
  Storage->>SDKResult: return stored failure payload
  SDKResult->>SDKResult: decode and attach structured details
  Storage->>Dashboard: provide stored error string
  Dashboard->>Dashboard: parse and render summary and traceback
Loading

Possibly related PRs

  • ByteVeda/taskito#268: Both changes evolve Node JobFailedError; this PR adds structured error decoding and metadata.
  • ByteVeda/taskito#293: Both changes overlap in Python error formatting around format_python_error.

Suggested labels: rust, dashboard, python, tests

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding structured task errors across the SDKs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/structured-task-error

Comment @coderabbitai help to get the list of available commands.

@pratyush618

pratyush618 commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 58 minutes.

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🧹 Nitpick comments (6)
crates/taskito-node/src/dispatcher.rs (1)

122-130: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Unnecessary clone of err.reason.

Since only one branch of the if/else executes, err.reason can be moved out directly instead of cloned.

♻️ Proposed diff
                 let reason = if err.reason.is_empty() {
                     err.to_string()
                 } else {
-                    err.reason.clone()
+                    err.reason
                 };
                 failure(job, reason, wall_time_ns, false)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-node/src/dispatcher.rs` around lines 122 - 130, Update the
reason selection in the dispatcher failure path to move the non-empty err.reason
value instead of cloning it. Preserve the existing err.to_string() fallback when
the reason is empty, and ensure the resulting owned value is still passed to
failure.
crates/taskito-core/BINDING_CONTRACT.md (1)

100-113: 🚀 Performance & Scalability | 🔵 Trivial

Consider capping traceback size for storage.

The contract lets traceback be an arbitrary array of strings serialized verbatim into jobs.error/job_errors.error/dead_letter.error. Deeply recursive or repeatedly-retried failures could blow up row size across all three tables with no documented cap or truncation guidance.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-core/BINDING_CONTRACT.md` around lines 100 - 113, Update the
canonical failure contract around JobResult::Failure.error to define a maximum
serialized traceback size and specify deterministic truncation behavior when the
limit is exceeded. Apply the cap before storing the error in jobs.error,
job_errors.error, or dead_letter.error, while preserving the required errtype,
message, and traceback keys.
crates/taskito-python/src/py_worker.rs (1)

227-239: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Silent fallback on encoder failure hides real bugs.

If taskito.task_errors fails to import, or encode_from_parts raises/returns something un-extractable as String, this silently falls through to plain-text traceback formatting with no log output. That's fine for a genuinely missing module, but if encode_from_parts throws due to an actual encoder bug (e.g. non-serializable traceback frame), you'll silently lose the structured error for every task failure in that process with no signal to investigate.

Consider logging at debug/warn level when the encode path fails after a successful import, to distinguish "module not installed" from "encoder is broken."

🔍 Proposed diff
     if let Ok(errors_mod) = py.import("taskito.task_errors") {
-        if let Ok(encoded) = errors_mod.call_method1(
+        match errors_mod.call_method1(
             "encode_from_parts",
             (e.get_type(py), e.value(py), e.traceback(py)),
         ) {
-            if let Ok(json) = encoded.extract::<String>() {
-                return json;
+            Ok(encoded) => {
+                match encoded.extract::<String>() {
+                    Ok(json) => return json,
+                    Err(err) => log::warn!("[taskito] encode_from_parts returned non-string: {err}"),
+                }
             }
+            Err(err) => log::warn!("[taskito] encode_from_parts failed: {err}"),
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-python/src/py_worker.rs` around lines 227 - 239, Update the
structured-error encoding flow around the task_errors import and
encode_from_parts calls to emit a debug or warning log when the module imports
successfully but encoding or String extraction fails, including the failure
details. Preserve the silent plain-text fallback only when taskito.task_errors
is genuinely unavailable, and keep returning the existing traceback fallback
after logging encoder failures.
dashboard/src/routes/workflows_.$id.tsx (1)

92-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Render TaskErrorSummary in the workflow error row. KvList already accepts a ReactNode, so this can stay one-line while keeping the tooltip used on other structured-error views.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/workflows_`.$id.tsx around lines 92 - 97, Update the
workflow error row in the data.error handling block to render the
TaskErrorSummary component for structured errors instead of converting them with
taskErrorSummary. Pass the resulting ReactNode as the KvList value so its
existing tooltip behavior is preserved, while continuing to use data.error for
unstructured errors.
dashboard/src/components/ui/task-error.tsx (1)

41-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using the app's Tooltip component instead of native title for consistency.

taskErrorTooltip can return multi-line traceback text; native title tooltips have inconsistent rendering/accessibility across browsers, whereas the dashboard already exports a Tooltip primitive (see dashboard/src/components/ui/index.ts) used elsewhere for hover content. Optional polish, not a blocker.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/components/ui/task-error.tsx` around lines 41 - 50, Update
TaskErrorSummary to use the dashboard’s Tooltip component for
taskErrorTooltip(parsed) content instead of the native title attribute,
preserving the existing summary text and fallback error behavior. Reuse the
exported Tooltip primitive and ensure the tooltip wraps the rendered span so
multi-line traceback content is displayed consistently.
sdks/node/test/core/task-error.test.ts (1)

24-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test vector doesn't exercise the real V8 stack format.

Manually setting error.stack bypasses V8's actual .stack shape (header line + frames), so this test can't catch the header-line duplication issue flagged in task-error.ts (Lines 22-27). Consider adding a case that lets a real thrown Error populate .stack naturally to pin down the expected frame-only (or header-inclusive) format.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdks/node/test/core/task-error.test.ts` around lines 24 - 30, Update the
encodeTaskError contract test to exercise a naturally populated V8 stack by
creating or throwing and catching the BoomError instead of manually assigning
error.stack. Assert the expected serialization against the actual
header-plus-frames stack format, ensuring encodeTaskError does not duplicate or
incorrectly retain the stack header.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/taskito-node/src/dispatcher.rs`:
- Around line 122-130: Add Node-side retry filtering in the dispatcher before
calling failure: evaluate the JS error against the configured retry policy, then
pass false for non-retryable exceptions and true otherwise. Extend the Node
error/API path as needed to propagate retry_on and dont_retry_on semantics from
the worker into the core result handler, while preserving the existing reason
construction and failure flow.

In `@sdks/java/src/main/java/org/byteveda/taskito/errors/TaskError.java`:
- Around line 27-30: Update TaskError.summary() to branch on message rather than
errtype, returning only errtype when the message is empty and preserving the
combined errtype-and-message format otherwise. Ensure the result matches the
shared summary contract and avoids a trailing colon or space.

In `@sdks/java/src/main/java/org/byteveda/taskito/errors/TaskErrors.java`:
- Around line 55-57: Update TaskErrors.decode() so missing or non-textual
errtype values default to "Error" instead of an empty string, while preserving
textual errtype values and the existing TaskError construction flow.

In `@sdks/java/src/test/java/org/byteveda/taskito/errors/TaskErrorsTest.java`:
- Around line 73-81: The test method summarizePrefersErrtypeMessageHeadline must
reflect the shared default errtype contract: update the missing-errtype
assertion in TaskErrors.summarize to expect the “Error” prefix, while preserving
the explicit errtype and raw-text fallback assertions.

In `@sdks/node/src/task-error.ts`:
- Around line 39-59: Update decodeTaskError to default missing or non-string
candidate.errtype to an empty string, matching the cross-SDK decoding contract.
Then update JobFailedError’s message construction in errors.ts to omit the
errtype separator when the decoded errtype is empty, while preserving the
existing “type: message” format for non-empty types.
- Around line 22-27: Update the errtype assignment in the Error handling branch
to prefer error.constructor.name, falling back to error.name and then "Error".
Keep the existing message and traceback handling unchanged.

In `@sdks/python/taskito/exceptions.py`:
- Around line 20-26: Update the JobErrorDetails docstring to accurately describe
legacy/plain-string errors: job_id and raw_error remain populated, while only
errtype and traceback default to None when the stored error is not structured
JSON. Keep the structured-error description unchanged.

---

Nitpick comments:
In `@crates/taskito-core/BINDING_CONTRACT.md`:
- Around line 100-113: Update the canonical failure contract around
JobResult::Failure.error to define a maximum serialized traceback size and
specify deterministic truncation behavior when the limit is exceeded. Apply the
cap before storing the error in jobs.error, job_errors.error, or
dead_letter.error, while preserving the required errtype, message, and traceback
keys.

In `@crates/taskito-node/src/dispatcher.rs`:
- Around line 122-130: Update the reason selection in the dispatcher failure
path to move the non-empty err.reason value instead of cloning it. Preserve the
existing err.to_string() fallback when the reason is empty, and ensure the
resulting owned value is still passed to failure.

In `@crates/taskito-python/src/py_worker.rs`:
- Around line 227-239: Update the structured-error encoding flow around the
task_errors import and encode_from_parts calls to emit a debug or warning log
when the module imports successfully but encoding or String extraction fails,
including the failure details. Preserve the silent plain-text fallback only when
taskito.task_errors is genuinely unavailable, and keep returning the existing
traceback fallback after logging encoder failures.

In `@dashboard/src/components/ui/task-error.tsx`:
- Around line 41-50: Update TaskErrorSummary to use the dashboard’s Tooltip
component for taskErrorTooltip(parsed) content instead of the native title
attribute, preserving the existing summary text and fallback error behavior.
Reuse the exported Tooltip primitive and ensure the tooltip wraps the rendered
span so multi-line traceback content is displayed consistently.

In `@dashboard/src/routes/workflows_`.$id.tsx:
- Around line 92-97: Update the workflow error row in the data.error handling
block to render the TaskErrorSummary component for structured errors instead of
converting them with taskErrorSummary. Pass the resulting ReactNode as the
KvList value so its existing tooltip behavior is preserved, while continuing to
use data.error for unstructured errors.

In `@sdks/node/test/core/task-error.test.ts`:
- Around line 24-30: Update the encodeTaskError contract test to exercise a
naturally populated V8 stack by creating or throwing and catching the BoomError
instead of manually assigning error.stack. Assert the expected serialization
against the actual header-plus-frames stack format, ensuring encodeTaskError
does not duplicate or incorrectly retain the stack header.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6c45b92-5e7a-4e06-a6f9-21e325e23be0

📥 Commits

Reviewing files that changed from the base of the PR and between dc7095c and 3abfd38.

📒 Files selected for processing (35)
  • crates/taskito-core/BINDING_CONTRACT.md
  • crates/taskito-node/src/dispatcher.rs
  • crates/taskito-python/src/native_async/task_executor.rs
  • crates/taskito-python/src/py_worker.rs
  • dashboard/src/components/ui/index.ts
  • dashboard/src/components/ui/task-error.tsx
  • dashboard/src/features/dead-letters/components/dead-letter-row.tsx
  • dashboard/src/features/dead-letters/components/dead-letter-table.tsx
  • dashboard/src/features/dead-letters/utils.test.ts
  • dashboard/src/features/dead-letters/utils.ts
  • dashboard/src/features/jobs/components/job-errors-tab.tsx
  • dashboard/src/features/jobs/components/job-overview-tab.tsx
  • dashboard/src/features/jobs/components/job-table.tsx
  • dashboard/src/features/workflows/components/workflow-nodes-table.tsx
  • dashboard/src/lib/task-error.test.ts
  • dashboard/src/lib/task-error.ts
  • dashboard/src/routes/workflows_.$id.tsx
  • sdks/java/src/main/java/org/byteveda/taskito/contrib/SentryMiddleware.java
  • sdks/java/src/main/java/org/byteveda/taskito/errors/TaskError.java
  • sdks/java/src/main/java/org/byteveda/taskito/errors/TaskErrors.java
  • sdks/java/src/main/java/org/byteveda/taskito/errors/package-info.java
  • sdks/java/src/main/java/org/byteveda/taskito/worker/WorkerDispatchBridge.java
  • sdks/java/src/test/java/org/byteveda/taskito/errors/TaskErrorsTest.java
  • sdks/java/test-support/src/test/java/org/byteveda/taskito/test/InMemoryQueueBackendTest.java
  • sdks/node/src/errors.ts
  • sdks/node/src/index.ts
  • sdks/node/src/task-error.ts
  • sdks/node/src/worker.ts
  • sdks/node/test/core/task-error.test.ts
  • sdks/python/taskito/async_support/executor.py
  • sdks/python/taskito/exceptions.py
  • sdks/python/taskito/prefork/child.py
  • sdks/python/taskito/result.py
  • sdks/python/taskito/task_errors.py
  • sdks/python/tests/core/test_task_errors.py

Comment thread crates/taskito-node/src/dispatcher.rs
Comment thread sdks/java/src/main/java/org/byteveda/taskito/errors/TaskError.java
Comment thread sdks/java/src/main/java/org/byteveda/taskito/errors/TaskErrors.java
Comment thread sdks/node/src/task-error.ts
Comment thread sdks/node/src/task-error.ts
Comment thread sdks/python/taskito/exceptions.py
@pratyush618 pratyush618 merged commit 680124a into master Jul 12, 2026
36 checks passed
@pratyush618 pratyush618 deleted the feat/structured-task-error branch July 12, 2026 06:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant