Skip to main content

Debugging User Code Errors

When task execution fails or workflows behave unexpectedly, errors in flyte-sdk fall into structured classifications. User-originated problems—such as type mismatch between return annotations and outputs, syntax or dependency failures during module loading, improper reference task invocations, and calls made outside a task execution context—raise subclasses of RuntimeUserError.

Understanding how flyte-sdk classifies errors, formats stack traces, and validates data allows you to quickly locate and fix issues in your task and workflow code.


Error Classification and Stack Trace Filtering​

flyte-sdk categorizes execution failures using BaseRuntimeError subclasses:

  • RuntimeUserError: Represents errors in user code, configuration, or data contracts (has kind="user").
  • RuntimeSystemError: Represents infrastructure, controller, or system-level communication failures (has kind="system").
  • RuntimeUnknownError: Represents unexpected exceptions that cannot be categorized (has kind="unknown").

When an unhandled Python exception occurs during task execution in _task.py, flyte-sdk wraps the exception into a RuntimeUserError:

try:
# Task execution
...
except RuntimeSystemError:
raise
except RuntimeUserError:
raise
except Exception as e:
raise RuntimeUserError(type(e).__name__, str(e)) from e

Revealing Full Tracebacks with Debug Logging​

By default, flyte-sdk registers a custom exception hook (sys.excepthook = custom_excepthook in _excepthook.py) that filters out internal engine frames matching _internal, syncify, and _code_bundle. This keeps tracebacks focused on your task code.

If you need to inspect full internal stack traces when debugging complex errors, set the logger level to DEBUG:

import logging
from flyte._logging import logger

logger.setLevel(logging.DEBUG)

When logger.getEffectiveLevel() <= logging.DEBUG, custom_excepthook bypasses filtering and delegates directly to Python's original exception handler.


Data Serialization and Type Validation Errors (RuntimeDataValidationError)​

flyte-sdk validates task outputs against their type annotations during output serialization in _internal/runtime/convert.py via convert_from_native_to_outputs. If validation or conversion fails, flyte-sdk raises RuntimeDataValidationError (error code "DataValiationError").

Issue 1: Missing Return Type Annotation​

If a task returns a value but does not define a return type annotation on the function signature (or annotates the return type as None), flyte-sdk expects zero outputs. Returning a value triggers a validation error.

Symptom​

RuntimeDataValidationError: In task calculate_sum variable o0, failed to serialize/deserialize because of Expected no outputs but got (15,),did you miss a return type annotation?

Problematic Code​

import flyte

@flyte.task
def calculate_sum(a: int, b: int):
# Missing return type annotation -> interface.outputs is empty
return a + b

Solution​

Add the return type annotation to the task definition:

import flyte

@flyte.task
def calculate_sum(a: int, b: int) -> int:
return a + b

Issue 2: Return Value Incompatible with Type Engine​

When the returned value cannot be transformed into a Flyte literal matching the annotated type (for instance, returning an incompatible data structure or a type unsupported by TypeEngine), TypeEngine.to_literal raises TypeTransformerFailedError, which is wrapped in a RuntimeDataValidationError.

Problematic Code​

import flyte

@flyte.task
def fetch_count() -> int:
# Returns a string where an integer is expected
return "not_a_number" # type: ignore

Solution​

Ensure returned values match the annotated types:

import flyte

@flyte.task
def fetch_count() -> int:
return 42

For multiple outputs, specify a tuple with matching return types:

import flyte

@flyte.task
def compute_metrics(val: int) -> tuple[int, str]:
return val * 2, f"Processed {val}"

Module Loading and Deployment Errors (ModuleLoadError)​

When deploying workflows via the CLI or loading tasks dynamically, flyte-sdk uses _utils/module_loader.py to discover and import Python files using importlib. If an import fails due to syntax errors, missing packages, or exceptions executing top-level module code, flyte-sdk raises ModuleLoadError (error code "ModuleLoadError").

Symptom​

ModuleLoadError: Failed to load module from /path/to/my_workflow.py: No module named 'missing_package'

Common Causes and Fixes​

  1. Top-Level Code Execution: Avoid executing heavy initialization, database connections, or network requests at the module's top level. Place setup logic inside task bodies or initialization functions.
  2. Missing Dependencies: Ensure all third-party libraries imported by the task files are installed in the deployment environment or specified in the container image requirements.
  3. Ignoring Unrelated Load Failures During Deployment: When running flyte deploy, flyte-sdk scans all Python files in the target directory by default. To skip files that fail to import (such as test utilities or uninstalled optional dependencies), pass the --ignore-load-errors flag:
flyte deploy --ignore-load-errors

Remote Reference Task Errors (ReferenceTaskError)​

ReferenceTaskError (error code "ReferenceTaskUsageError") occurs when interacting with remote tasks via flyte.remote.Task or TaskDetails.

Issue 1: Task Not Found When Resolving Remote Version​

When retrieving a remote task with Task.get(name="...", auto_version="latest"), flyte-sdk queries the remote control plane for the latest task version. If the task does not exist in the specified project and domain, flyte-sdk raises ReferenceTaskError.

Problematic Code​

import flyte

# Raises ReferenceTaskError if 'non_existent_task' is not deployed
remote_task = flyte.remote.Task.get(
name="non_existent_task",
project="flytesnacks",
domain="development",
auto_version="latest",
)

Solution​

Verify that the task name, project, and domain exist on the remote control plane, or deploy the task before referencing it:

import flyte

remote_task = flyte.remote.Task.get(
name="my_deployed_task",
project="flytesnacks",
domain="development",
version="v1.0.0", # Or auto_version="latest" once deployed
)

Issue 2: Passing Positional Arguments to Reference Tasks​

Reference tasks do not accept positional arguments. Calling a reference task with positional arguments raises ReferenceTaskError.

Problematic Code​

# Raises ReferenceTaskError: Reference task my_deployed_task does not support positional arguments currently.
res = await remote_task(10, "value")

Solution​

Pass all inputs as keyword arguments:

res = await remote_task(x=10, name="value")

Issue 3: Executing Reference Tasks in Local Controller​

Reference tasks represent remote task definitions and cannot be executed locally under LocalController. Invoking submit_task_ref locally raises ReferenceTaskError("Reference tasks cannot be executed locally, only remotely.").

Solution​

Run workflows containing reference tasks remotely or mock the reference task during local development and testing.


Task Context Misuse (NotInTaskContextError)​

NotInTaskContextError is raised when calling context-dependent operations that require an active TaskContext outside of an executing task.

In _internal/controllers/_local_controller.py, methods such as get_action_outputs and record_trace verify that internal_ctx().data.task_context is initialized:

ctx = internal_ctx()
tctx = ctx.data.task_context
if not tctx:
raise flyte.errors.NotInTaskContextError("BadContext", "Task context not initialized")

Similarly, calling Task.get(..., auto_version="current") requires an active task context to resolve the version of the currently executing task. If called outside a task, it raises a ValueError.

Problematic Code​

from flyte.remote import Task

# Attempting to resolve "current" auto_version outside of an active task context
task_ref = Task.get(
name="sub_task",
project="flytesnacks",
domain="development",
auto_version="current", # Fails: no active task context exists
)

Solution​

Only request auto_version="current" inside the body of an active task where flyte.ctx() contains an initialized TaskContext:

import flyte
from flyte.remote import Task

@flyte.task
async def parent_task(x: int) -> int:
# Valid: auto_version="current" resolves the version from the executing task context
sub_task = await Task.get(
name="sub_task",
project="flytesnacks",
domain="development",
auto_version="current",
)
return await sub_task(val=x)

For code executing outside tasks (such as setup scripts or workflow definition scripts), pass explicit versions or use auto_version="latest".