Why does HubSpot say “Task timed out after 20.02 seconds”?
HubSpot's custom code actions stop after 20 seconds and 128 MB, and that ceiling cannot be raised on any plan. The fix is to move the slow work out of HubSpot's runtime: an app-provided workflow action can pause the enrollment, run for as long as it needs, then resume the workflow with its results.
Last updated
If your workflow history shows this:
Task timed out after 20.02 secondsyour custom code action hit HubSpot’s fixed execution ceiling. Nothing in your code is malformed. It simply ran out of time.
What the limit actually is
HubSpot’s native custom code actions run in a managed runtime with two hard limits:
- 20 seconds of wall-clock execution
- 128 MB of memory
Both are platform-level and non-negotiable. They are identical on Professional and Enterprise, they can’t be raised by support, and no setting in the workflow editor changes them. If your job needs 25 seconds, the native action is the wrong place to run it. No amount of tuning changes that.
The memory limit produces a different, less obvious failure: an out-of-memory kill often surfaces as a generic failure rather than a clear message, which is why large CSV parsing or big API responses tend to fail confusingly rather than loudly.
What’s usually eating the 20 seconds
In rough order of how often each one is the culprit:
- Sequential API calls. Five HubSpot calls at 300 ms each is 1.5 seconds; thirty of them is nine. Pagination through an association list is the classic offender.
- A slow third party. Enrichment providers, address validators and payment APIs routinely take 2–10 seconds, and their p99 is much worse than their average. Your action inherits their bad days.
- Rate-limit backoff. If you’re retrying on a 429, the sleep counts against your 20 seconds.
- Per-record work that should be batched. Running the same expensive setup once per enrolled record instead of once per batch.
- Cold dependencies. Anything that builds a large in-memory structure before doing useful work.
A useful diagnostic: log a timestamp before and after each external call, run the action once, and read the workflow history. The gap that dominates is your answer, and it’s rarely the part you suspected.
Workarounds that genuinely help
Cut the work per execution. Instead of processing every associated deal in one action, enroll each deal in its own workflow and process one per execution. This works, and it’s the right answer when the work is naturally per-record.
Batch outside the action. Set a flag property, and have a scheduled process do the heavy lifting later. You trade immediacy for reliability.
Fetch less. Request only the properties you need, and use search endpoints with filters rather than pulling a list and filtering in code.
These are real fixes. But all three share a limitation: they work by making the job small enough to fit, which is only possible when the job is divisible. Waiting on a third-party API that takes 45 seconds isn’t divisible. Neither is generating a document, running a long report, or calling a model with a large context.
Workarounds that don’t work
Firing an async request and returning early. Once your action responds, HubSpot tears down the execution context. Anything still in flight is killed. This appears to work in testing. The action reports success, and then silently drops work in production.
Splitting into several custom code steps. Each step gets its own 20 seconds, so this helps if the work is genuinely sequential and divisible. It does not help with a single slow call, and it multiplies your maintenance surface.
Retrying. If the work deterministically takes more than 20 seconds, every retry fails identically, and each attempt re-runs side effects that already completed. Retrying a timeout can double-charge a customer.
Running the work somewhere with no 20-second ceiling
HubSpot supports app-provided workflow actions. Actions that a connected app registers, which then appear in the workflow editor alongside the native ones. When one executes, HubSpot sends the enrollment payload to the app, and the app can respond with a pause instead of a result.
That pause is the important part. The action returns immediately (which is all HubSpot needs), the enrollment holds, the work runs on the app’s infrastructure for as long as it takes, and the workflow resumes when the app reports back. The pause window extends up to about a week, so the practical limit becomes your runtime, not HubSpot’s.
This is a documented, supported part of the platform rather than a trick. It’s the same mechanism behind every long-running integration action you’ve used.
Where Turbyn fits
Turbyn is that app, aimed specifically at people who were writing custom code and hit the ceiling:
- Runtime measured in minutes, not seconds. Long third-party calls, document generation and multi-page pagination all fit.
- Your code, essentially unchanged. Turbyn uses the same
exports.main = async (event) => {}shape and the sameeventobject, so an action that was timing out usually runs as-is. - Any npm or pip package, rather than the fixed handful HubSpot allows.
- A test loop that shows you the timing. Run against a real record, watch the console stream, and see exactly which call is slow. Before it’s live.
exports.main = async (event, ctx) => { // A 45-second enrichment call. Fine here; impossible in a 20-second action. const profile = await enrich(event.inputFields.domain);
ctx.log.info("enriched", { domain: event.inputFields.domain });
return { outputFields: { output1: profile.industry, output2: String(profile.employeeCount), }, };};If your action is failing at 20.02 seconds and the work genuinely can’t be cut smaller, that’s the situation Turbyn was built for.
FAQ
Frequently asked
Can I increase the 20-second limit on HubSpot custom code actions?
No. The 20-second execution limit and 128 MB memory limit on HubSpot's native custom code actions are fixed platform limits. They are not configurable, they do not change by plan tier, and HubSpot support cannot raise them for an individual portal.
Why does the error say 20.02 seconds instead of 20?
The number is the measured wall-clock time at the moment the runtime was killed, not the limit itself. It is almost always a few hundredths over 20 seconds because the timer fires and then the termination is recorded. A value like 20.02 or 20.05 means you hit the ceiling, not that you were narrowly over an adjustable budget.
Does retrying the action help?
Only if the timeout was caused by a slow external dependency that has since recovered. If your code consistently needs more than 20 seconds, every retry will fail the same way, and each one re-runs whatever side effects already completed before the timeout.
Does an async or background call let me get around the limit?
No. Returning early while leaving work running in the background is unreliable. HubSpot terminates the execution container once the action responds, so anything still in flight is killed. Work that outlives the response has to run somewhere other than HubSpot's runtime.