Troubleshoot Functions
Work from the symptom you can see. Most failures fall into one of three places: the code was rejected when you saved it, the execution failed when it ran, or the execution never happened at all.
Start with three checks
- Is the function active? Execute matches active functions only, so an inactive function returns 404 exactly like a missing one.
- Did a run happen? Every execution creates an invocation record. No record means the function never started.
- Which version ran? Records store the version that executed. A function that behaves unexpectedly may be running code you have since changed.
A function will not save
| Message | What it means | Fix |
|---|---|---|
Code must define main(params, user_data, sdk_client) | Validation searches for that literal text. It is a string match, not a parse, so annotations, renamed parameters, or different spacing fail even when the function is valid Python | Copy the signature line exactly, and rename parameters inside the body if you prefer |
| Code compilation failed | The source is not valid Python, or uses a language feature the sandbox does not allow | Read the named error. If the syntax is valid Python, rewrite the construct using simpler statements |
webhook_url is required for PROXY mode | A proxy function was saved without a destination | Supply a URL with a scheme and host |
| Code is required for APP mode functions | An app function was saved with no code | Add code, or change the execution mode |
| Invalid CEL syntax | A filter condition could not be parsed | Correct the expression. See Filter conditions |
A function saves but fails to run
| Message | What it means | Fix |
|---|---|---|
ImportError, listing allowed modules | The code imported a module outside the sandbox allowlist. Imports are checked when they run, so an import inside a branch fails only when that branch executes | Use a permitted module, or move the work to a proxy function |
main must define a function | main exists but is not callable, or was set to None | Define main as a function at the top level of the module |
main() accepts N parameters but must accept exactly 3 | The runtime signature check found the wrong arity | Take exactly params, user_data, and sdk_client |
A NoneType attribute or call error naming the SDK | A method was called on the wrong SDK attribute | Check the client path, such as sdk_client.database rather than a misspelling |
| The execution timed out | Synchronous work exceeded the execution wait, 900 seconds by default | Execute asynchronously and collect the result by task id |
Results are not what you expect
| Symptom | Why | What to do |
|---|---|---|
| An update returned 200 but nothing changed | Versions are created only when a versioned field actually changes. A request matching the stored values returns a success status with an error body reading No changes detected, version unchanged | Confirm the field and value differ. Read the body rather than the status code |
| Reverting restored the code but not the configuration | Revert restores code and filter_conditions only. Other versioned fields keep their current values | Read the history and set the remaining fields explicitly after reverting |
| The version number went up after a revert | Restoring code is itself a change, so the counter advances rather than rewinding | Expected behavior. History is append-only |
| The output does not match the source you are reading | The run used an earlier version | Read the single invocation through the function-scoped endpoint, which returns the code that actually ran |
| A proxy function reports success for a failed call | A non-2xx webhook response is returned rather than raised | Check status_code rather than assuming a result means success |
| The log ends mid-run | A limit was reached: 2,000 entries, 1 MiB total, or 10,000 characters in one message | Log decisions and failures rather than every iteration |
A run never happened
An event-triggered function that produces no invocation record at all was skipped by its filter conditions. A skipped execution dispatches no task and creates no record, so nothing marks the attempt.
A condition that errors counts as not matching. An expression that references a field the event doesn't carry, divides by zero, or uses an invalid regular expression resolves to false and skips the function without an error.
Check the expression against the event's actual parameters. An absent or empty expression permits execution, so a function that stopped running usually gained a condition rather than losing one. Filter conditions apply to event triggers only: if the function runs when called directly but not from an event, the condition is the cause.
Access is refused
A 403 has two causes. The function is not public and the request carried no credentials, or policy denied an authenticated caller.
Send credentials first. If you are already authenticated, confirm your permission
to execute that function. Do not set is_public to work around a permission
problem — it makes the function executable by anyone, with no policy check at all.
See Security and limits.
A 404 from execute means the app slug is wrong, the function slug is wrong, or the function is inactive. Slugs are generated from the name and may carry a numeric suffix when another function claimed the same one.
Functions are unavailable for the site
Executions are rejected without retry when the organization's account is not active. The task is refused rather than queued, so no result appears.
Check the organization's billing state before investigating the function itself.
Prepare a support request
Capture these, and confirm none carries a secret before sharing:
- The app slug and function slug.
- The
celery_task_idof a failing run. - The invocation record, including its status and captured logs.
- The version that ran, from the record's history id.
- Whether the same call succeeds synchronously and fails asynchronously, or the reverse.