Troubleshooting

“Command not found” errors

If you installed Modal but you’re seeing an error like modal: command not found when trying to run the CLI, this means that the installation location of Python package executables (“binaries”) are not present on your system path. This is a common problem; you need to reconfigure your system’s environment variables to fix it.

One workaround is to use python -m modal.cli instead of modal. However, this is just a patch. There’s no single solution for the problem because Python installs dependencies on different locations depending on your environment. See this popular StackOverflow question for pointers on how to resolve your system path issue.

Custom types defined in __main__

Modal currently uses cloudpickle to transfer objects returned or exceptions raised by functions that are executed in Modal. This gives a lot of flexibility and support for custom data types.

However, any types that are declared in your Python entrypoint file (The one you call on the command line) will currently be redeclared if they are returned from Modal functions, and will therefore have the same structure and type name but not maintain class object identity with your local types. This means that you can’t catch specific custom exception classes:

import modal
app = modal.App()  # Note: prior to April 2024, "app" was called "stub"

class MyException(Exception):
    pass

@app.function()
def raise_custom():
    raise MyException()

@app.local_entrypoint()
def main():
    try:
        raise_custom.remote()
    except MyException:  # this will not catch the remote exception
        pass
    except Exception:  # this will catch it instead, as it's still a subclass of Exception
        pass

Nor can you do object equality checks on dataclasses, or isinstance checks:

import modal
import dataclasses

@dataclasses.dataclass
class MyType:
    foo: int

app = modal.App()  # Note: prior to April 2024, "app" was called "stub"

@app.function()
def return_custom():
    return MyType(foo=10)


@app.local_entrypoint()
def main():
    data = return_custom.remote()
    assert data == MyType(foo=10)  # false!
    assert data.foo == 10  # true!, the type still has the same fields etc.
    assert isinstance(data, MyType)  # false!

If this is a problem for you, you can easily solve it by moving your custom type definitions to a separate Python file from the one you trigger to run your Modal code, and import that file instead.

# File: my_types.py
import dataclasses

@dataclasses.dataclass
class MyType:
    foo: int
# File: modal_script.py
import modal
from my_types import MyType

app = modal.App()  # Note: prior to April 2024, "app" was called "stub"

@app.function()
def return_custom():
    return MyType(foo=10)

@app.local_entrypoint()
def main():
    data = return_custom.remote()
    assert data == MyType(foo=10)  # true!
    assert isinstance(data, MyType)  # true!

Function side effects

The same container can be reused for multiple invocations of the same function within an app. This means that if your function has side effects like modifying files on disk, they may or may not be present for subsequent calls to that function. You should not rely on the side effects to be present, but you might have to be careful so they don’t cause problems.

For example, if you create a disk-backed database using sqlite3:

import modal
import sqlite3

app = modal.App()  # Note: prior to April 2024, "app" was called "stub"

@app.function()
def db_op():
    db = sqlite3("db_file.sqlite3")
    db.execute("CREATE TABLE example (col_1 TEXT)")
    ...

This function can (but will not necessarily) fail on the second invocation with an

OperationalError: table foo already exists

To get around this, take care to either clean up your side effects (e.g. deleting the db file at the end your function call above) or make your functions take them into consideration (e.g. adding an if os.path.exists("db_file.sqlite") condition or randomize the filename above).

413 Content Too Large errors

If you receive a 413 Content Too Large error, this might be because you are hitting our gRPC payload size limits.

The size limit is currently 100MB.

403 errors when connecting to GCP services.

GCP will sometimes return 403 errors to Modal when connecting directly to GCP cloud services like Google Cloud Storage. This is a known issue.

The workaround is to pin the cloud parameter in the @app.function or @app.cls.

For example:

@app.function(cloud="gcp")
@app.cls(cloud="gcp")