My goal:
Our workflow (id bb51c6cb-ccd1-4374-b7e4-9d64691f8a88) posts to an API. A Loop block iterates over the payment lines and, for each item, calls a single-step REST Function block. The loop runs sequentially with error policy "ignore" and a retry policy (5 attempts, 1s initial interval, backoff Ă—2, 10s max). The function also has its own retry policy (5 attempts). When the API rejects a payment, the iteration should fail, be retried under that policy, and come back as null if every attempt fails. That's how we detect a payment that didn't post.
Issue:
Since mid-September, a non-2xx response inside a loop iteration is no longer treated as an error. The upstream API returns HTTP 401 Unauthorized, Content-Type: application/json, body {"error":"error reason"}. Retool's own metadata for that iteration records status: 401, isError: true, error: "Unauthorized". But:
- the iteration is recorded as a success, and the raw error body becomes the iteration's result (data: [{"error":"error reason…"}]);
- there's no "Error evaluating functionName" log line, and neither retry policy fires: exactly one request is made;
Before mid-September the same response behaved correctly. On 11 Sep the run log shows Error evaluating functionName (iteration 1): {"error":"Login failed…"}, then Retrying query functionName (attempt 2/3) in 1000 ms and (attempt 3/3). The iteration ended as null, and the error was wrapped as {statusCode: 400, error: "Bad Request", …, status: 401}.
The practical impact is that a failed payment looks like a successful one. Downstream logic treats it as posted unless it explicitly checks the body.
Steps I've taken to troubleshoot:
- Reviewed block-level logs for every failed run of this workflow back to July. The upstream response is identical in all of them: 401, application/json, content-length 59, the same JSON body. Only Retool's handling of it changed.
- Pinned down when it changed. Error and retries on 8 Jul and 11 Sep; returned as a success on 16 Sep, 18 Sep and twice on 1 Oct.
- Diffed our workflow definition across that window. The only change between the last good run (11 Sep) and the first bad one (16 Sep) was raising the loop's retry Attempts from 3 to 4 (saved 2026-09-12T07:18Z). The function block, its resource and the request are otherwise unchanged. So we can't find a change on our side that would explain the new behaviour. The function block, its resource and the request are otherwise unchanged. So we can't find a change on our side that would explain the new behaviour.
- Worked around it by adding a JS block after the loop that inspects each iteration's result for an error key and re-calls the function directly with its own backoff. We'd rather rely on the loop's built-in retry policy.
Questions:
- Did the handling of non-2xx responses in function-backed loop iterations change around 12–16 Sep 2026?
- What's the supported way to make such an iteration fail so the retry policy applies?
Additional info:
- Retool Cloud (highfieldsvets.retool.com)
- Workflow: id bb51c6cb-ccd1-4374-b7e4-9d64691f8a88.
- Correct behaviour (iteration errored and was retried / null):
- 019f42f6-276d-752a-804c-b2fd08d3139b, 2026-07-08 18:20 UTC: 401 login body treated as an error, iteration null
- 01a09134-a50e-750d-856b-678b16e03fc4, 2026-09-11 16:02 UTC: 401 treated as an error, retried attempts 2/3 and 3/3, iteration null
- Incorrect behaviour (401 returned as a successful iteration, no retry, transformer not run):
- 01a0aaf5-e87b-754d-8d35-1083f11f494d, 2026-09-16 16:03 UTC
- 01a0b494-06af-716c-8e6a-f9fa67ac6ee6, 2026-09-18 12:53 UTC (first run after the function's transformer was enabled)
- 01a0f6ef-cf91-740b-a6da-74780c0a096c, 2026-10-01 10:08 UTC
- 01a0f85b-7716-76bb-b8ef-5dc199f37733, 2026-10-01 16:45 UTC
