The entry function
A script exports one async function. The runtime calls it with a single object.
Notes on the shape:
- CommonJS is the norm:
module.exports = async (...) => { ... }.export defaultworks too, and an ESM file with top-levelawaitis loaded as a module. - A node with an input schema must destructure
args. Theasync (page) => { ... }form does not receiveargsand fails validation when the node declares inputs. - The return value decides output and routing. The golden path is to return
asteroid.handoff(...), which chooses the next node and carries its data. An object instead becomes output variables, a string becomesscript_output, and a throw is a failure. See Route to the next node yourself.
Timeouts
Inside that budget, Playwright actions carry their own defaults, kept short so a wrong selector fails fast instead of stalling.
Raise a limit for a genuinely slow step:
The sandbox
A script runs in the execution’s sandbox, next to the browser.- Node.js 22. Node built-in modules (
node:fs,node:path,node:crypto, and the rest) are available. - Playwright drives a headless Chromium over the live session. You get it through
page,browser, andcontext, so you do not launch a browser yourself. python3is present, withpython3-yaml, reachable from the workflow’s Bash tool.- No PDF or OCR libraries. To read a PDF or a print-only page, parse it in the browser page with
pdf.js, not with a Node module. See the PDF pattern.
The filesystem
The working directory is/home/agent. A script reads and writes files with node:fs, using absolute paths.
A script’s own file lives under
/home/agent/shared/<node-slug>/, where the runtime derives <node-slug> from the node’s display name. Because it re-derives the slug, a rename never breaks how the runtime finds the entry script. It does not, however, rewrite absolute paths you wrote by hand. A hardcoded /home/agent/shared/<node-slug>/... path (a required helper, a reference file) breaks when you rename the node, so update those yourself.
Workflow filesystem
The full directory layout, the quotas, and how files get in and out
Requiring modules
require('asteroid')is provided by the runtime. Its whole surface ishandoff(...),generateTotp(...)andemit(...). See Route to the next node yourself, Two-factor codes and Emit your own event.- Node built-ins resolve normally.
- Helper modules must be required by their absolute path under
shared/, for examplerequire('/home/agent/shared/file_claim/lib/dates.js'). A relativerequire('./dates.js')does not resolve, because the runtime runs your script from a private copy that holds no sibling files. - Use absolute
/home/agent/...paths. They work exactly as written from the entry script. Pass a required helper the paths it needs as arguments, rather than letting it compute its own, so it never depends on where the runtime placed your script.
Secret tokens
A script fills a secret with a##ITEM_KEY.FIELD## token. The runtime replaces it from the
secret attached to the profile, just before the script runs, so the value never reaches the model.
The usage is in Secrets in a script, and the full
model is in Secrets & 2FA.
Two-factor codes
asteroid.generateTotp(secret, options?) returns the current authenticator code as a string. Pass
the token of a TOTP seed field. The runtime fills the token before the script runs, so the seed
stays inside the script run. Only the generated code reaches the page.
The secret may be a bare base32 seed or an
otpauth://totp/ URI. A URI’s own digits, period
and algorithm apply unless an option overrides them.
- A token the run does not fill throws, and the error names the token.
- An unusable seed throws a fixed message that never quotes the seed.
- Generate the code immediately before you submit it. If the site rejects it, generate a fresh code and retry once.
Emit your own event
await asteroid.emit(name, options?) publishes an event from inside the run. It reaches every
webhook and Slack integration on the workflow’s Notifications tab that subscribes to
Custom Event, either every custom event or those whose name matches a filter. The script
holds no token and no URL.
Files are sent by reference. The runtime uploads each one before the event goes out, and the
event carries a signed download link that works for one hour. Slack shows an image inline and
any other file as a link. A webhook gets the link in
files[]. A file under shared/ cannot be
attached: that directory is read-only and never synced from a run.
- Await the call. It resolves to
{ id }. Delivery to your endpoint happens after that, so a slow or failing endpoint never fails the script. - A malformed call throws, and the error names the argument. So does the 101st emit of one execution.
- Do not send an event again after “did not confirm”. That error means the runtime gave no
answer in time. The event can still arrive, and a second call is a second event with a new
id. - A secret never leaves the run. If
textorpayloadcontains the value of a secret from the profile, the runtime swaps it for its'##ITEM_KEY.FIELD##'placeholder before the event is sent, so the receiver sees the placeholder, not the value. Anameor a file path that contains a secret value is refused. File contents are sent as written. - A test-run in Astro sends nothing. It checks the call, logs the event, and resolves to
{ id: null, dryRun: true }. - Do not use it for the final result. A run that fails, is cancelled or times out never reaches
your
emit.EXECUTION_COMPLETED,EXECUTION_FAILEDandEXECUTION_CANCELLEDalways fire. Useemitfor progress and for content the lifecycle events do not carry.
Related
Script a step
Attach a script, route from it, and pin its inputs
Workflow filesystem
Directories, quotas, and file transfer
Secrets & 2FA
How a
##ITEM_KEY.FIELD## token gets its valueNodes
Where a script file lives on a node

