Lifecycle & limits¶
This page covers what Cascade does with a script from the moment it loads until it is deleted: the four status values you will see in the Scripts panel, the watchdog that bounds handler execution, the sandbox rules, and when to graduate to a plugin.
Lifecycle¶
A script moves through the following stages:
- Load — Cascade discovers your script folder under
~/.cascade-chat/scripts/, compiles all.gofiles in it, and callsSetup(if defined). The status becomesloaded. - Run — Cascade dispatches incoming events (
OnText,OnJoin, etc.) and fires any timers yourSetupregistered. The script stays here until something changes. - Hot-reload on save — When you save a source file, Cascade automatically reloads the script. The interpreter is recreated:
Setupruns again, and all timers are re-registered from scratch. - Enable / disable — You can toggle a script off in the Scripts panel. Disabling it stops all dispatch and cancels timers. Re-enabling it runs
Setupagain, exactly as a fresh load. - Unload — Deleting the script folder removes it entirely. All timers stop and the script disappears from the panel.
stateDiagram-v2
[*] --> loaded: load, Setup OK
[*] --> error: compile / import / Setup panic
loaded --> loaded: save (hot-reload)
loaded --> disabled: toggle off
disabled --> loaded: toggle on (re-runs Setup)
loaded --> runaway: handler exceeds 2s
loaded --> runaway: 3 failed dispatches
error --> loaded: fix source and save
runaway --> loaded: Enable (resets strikes)
loaded --> [*]: delete folder
disabled --> [*]: delete folder
Package-level state does not survive a reload
The interpreter is recreated on every reload or enable. Package-level variables are reset to their zero values, so do not rely on in-memory state persisting across a save.
Status values¶
The Scripts panel shows one of four statuses for each script:
| Status | Meaning |
|---|---|
loaded |
Running normally; events are being dispatched. |
disabled |
Turned off, either by you in the panel or persisted off from a previous session. No events are dispatched and no timers run. |
error |
Failed to load. A compile or type error, a forbidden import, or a Setup panic prevented the script from starting. The panel shows the error message. |
runaway |
Auto-disabled by the watchdog (see below). The panel shows the reason. Re-enable from the panel to reset the strike count and re-run Setup. |
The watchdog: keep handlers fast¶
Handlers must complete within 2 seconds
Every handler call and every timer callback is bounded to a 2-second deadline. If a handler exceeds that deadline, Cascade immediately marks the script as runaway and stops dispatching further events to it.
Stopping dispatch contains the damage, but Cascade cannot kill an already-spinning goroutine. A handler that hangs keeps consuming a goroutine until the process exits. Never block, sleep-loop, or run unbounded work inside a handler.
Separately, 3 consecutive failing dispatches (including panics) also auto-disable the script as runaway. A single successful dispatch resets the strike counter. You do not have to exceed the deadline to hit this limit: a handler that panics three times in a row is disabled.
To recover a runaway script: click Enable in the Scripts panel. That clears the strike count and re-runs Setup.
What "fast" means in practice:
- Do not call
time.Sleepor any blocking I/O. There is notimepackage in the sandbox, so code that imports it fails to load with anerrorstatus. It never reaches the point of being blocked at runtime. - Do not loop over unbounded input inside a handler. If you need to process a large data set, break it into smaller chunks driven by timers.
- Return from handlers as soon as you have dispatched a reply; do not wait for confirmation.
The sandbox¶
Only the cascade package is available
Scripts can import only github.com/matt0x6f/irc-client/cascade. There is no standard library inside the script interpreter: no fmt, no strings, no os. Importing any other package is a load-time error, and the script status shows error.
This is the security model: a script can do exactly what the cascade API allows and nothing more. The API deliberately limits you to IRC operations (reply, say, join/part via timers) so a script cannot read files, make arbitrary network connections, or spawn processes.
You still have Go built-ins (string concatenation with +, len, append, switch, range, maps, and slices) plus the full event, reply, and timer surface from the cascade package. See Writing scripts for the complete list of available types and methods.
Permissions (v1)¶
The // cascade:permissions header is parsed and displayed in the Scripts panel alongside the script name and description. For example:
In v1, permissions are not enforced. They are a forward-looking declaration, a signal to you and other users about what capabilities the script intends to use. Do not treat the permissions list as a security boundary in v1; enforcement will be added in a future release.
When to use a plugin instead¶
Scripts are designed for personal, single-user automation inside the Cascade sandbox. If you need any of the following, graduate to a plugin:
- Real process isolation — plugins run as separate subprocesses; a crashing plugin cannot take down Cascade.
- Third-party dependencies — plugins can import any Go or other-language library.
- Untrusted or hostile input — if you are processing input from external sources that could be adversarial, the plugin subprocess boundary provides a much stronger containment model than the script watchdog.
- Persistent background I/O — long-lived connections (webhooks, websocket listeners, database access) fit naturally into a plugin's process model.