Skip to main content

Limits and Errors

When a script fails, the platform categorizes the error, writes it into the execution logs, and returns the error message as the tool's result. This page covers the error taxonomy, how failures surface to agents and to you, what execution limits actually exist, and the workflow for debugging a misbehaving script.

Error taxonomy​

Every failed execution is categorized into one of five types, based on the error the engine reported:

  • Name
    JS_SYNTAX_ERROR
    Type
    category
    Description

    The script didn't parse — the engine reported a SyntaxError (including the await is only valid in async functions case from top-level await). Nothing ran; there are no logs from your code, only the error itself.

  • Name
    JS_REFERENCE_ERROR
    Type
    category
    Description

    A ReferenceError — some identifier is not defined. The usual suspects: browser/Node APIs that don't exist in the sandbox (fetch, setTimeout, require), a typo'd helper name, or responseStream in a non-streaming execution.

  • Name
    JS_TYPE_ERROR
    Type
    category
    Description

    A TypeError — calling something that isn't a function, reading a property of undefined. Frequently the downstream symptom of a missed JSON.parse (treating an envelope string as an object).

  • Name
    JAVA_RUNTIME_ERROR
    Type
    category
    Description

    The failure happened in platform code around the script rather than inside the JavaScript engine — e.g. the execution environment itself failed. Rare; not something your script logic caused.

  • Name
    UNKNOWN_ERROR
    Type
    category
    Description

    Anything else the engine threw that doesn't match the patterns above — including your own throw new Error(...), other JS error types like RangeError, and errors thrown out of helper methods (say, mathUtils.divide by zero or an interrupted waitUtils.sleep).

For the three JS_* categories, the categorized message is appended to the execution logs as an entry like [JS_REFERENCE_ERROR] ReferenceError: fetchOrders is not defined, so the logs always name the failure even when you logged nothing yourself.

How errors surface​

Three layers work together:

  1. The wrapper logs the failure point. Every script runs inside a platform-added try/catch that calls console.error with the error's stack trace (or name and message) before re-throwing. Runtime failures therefore always leave a trace in the logs, even if your code has no try/catch at all.
  2. The tool result becomes the error message. For a script tool, a failed execution returns the raw error text (e.g. TypeError: Cannot read property 'orders' of undefined) as the tool's result — that is what the agent sees, and what it will react to or retry on.
  3. The debugging info carries everything. Each execution's debugging info is the full execution result: the success flag, the final response, the error, and the complete log list (your console.* output, the wrapper's stack trace, the categorized error line). This is what the test window shows you.

In computed columns there is no tool response to carry the error: a failed script simply writes no value, and the column stays empty.

Errors inside helpers are different from script errors: api.*, llm.invoke, connector.*, and tools.invokeById report their failures as error envelopes in their return values (errorCode/errorMessage fields), not as thrown exceptions. Your script keeps running — which is why every helper page tells you to check the envelope before using the result. An unchecked envelope usually resurfaces two lines later as a JS_TYPE_ERROR.

Forgetting response.setResponse​

A script that finishes without calling response.setResponse(...) is not an error — the execution succeeds with an empty result:

  • A script tool returns nothing to the agent, which typically leaves it confused about whether the tool worked.
  • A computed column treats the empty result as "no value" and writes nothing (occasionally useful — see Computed columns for the leave-unset pattern).

Make setResponse unconditional: every branch, including error branches, should set something, even if it's JSON.stringify({ error: "..." }).

Limits​

The sandbox itself imposes no CPU, memory, or wall-clock cap on your script's own code — a plain infinite loop is bounded only by the platform's surrounding task limits, not by an in-engine timer. The practical limits live in the helpers and the platform around the execution:

  • api.* — 30 seconds to establish a connection, 60 seconds for the full request; a timeout comes back as an IO_ERROR envelope. See API helper.
  • llm.invoke — 120 seconds per call; a timeout is an INVOCATION_ERROR envelope. See LLM helper.
  • tools.invokeById — no timeout of its own, but each call synchronously runs the invoked tool's full pipeline, inheriting whatever limits that tool's type carries.
  • waitUtils.sleep — uncapped, but it blocks the whole script and burns the surrounding execution's time budget.
  • The surrounding execution — script tools run inside an agent task or API call, and the platform's task-level limits bound the total time. A script that outlives its caller gets its threads interrupted, which surfaces as INTERRUPTED-style helper errors or an UNKNOWN_ERROR.

There are no enforced limits on script length, log volume, or response size beyond those platform task limits.

Debugging workflow​

  1. Log liberally with console.*. Log inputs on entry, intermediate values after each helper call, and decisions at each branch. Remember plain object logging coerces to an opaque string — log JSON.stringify(obj). Every entry lands timestamped and leveled in the execution logs.
  2. Run it in the test window. The tool builder's test window executes the script with variables you supply and shows the full debugging info — result, error, and the complete log list, including the wrapper's stack trace and the categorized [JS_*] line for failures. Iterate here, not inside a live agent conversation.
  3. Hand it to Script Copilot. Script Copilot sits in the script editor, sees your script and helpers, and can diagnose a failing run or rewrite the script against the error you paste in.

Two habits prevent most failures in the first place: parse every helper return (envelope first, body second) and check its error fields before touching the data; and validate variables up front, returning a structured error early — see the validation recipe for the pattern.