Scripting Guide

Lesson 12: Error Handling, Limits, and Performance

How failures surface in GuildScript, the limits every run lives under, and how to write agents that behave well at the edges.

The two kinds of failure

GuildScript separates failures into two channels, and knowing which is which saves hours of debugging:

  • Action failures do not throw. Every Discord action (reply, roles.add, channels.fetch, ...), every db call, and every llm call catches its own errors and returns a value with an error property (or a fallback like null or []). Your code keeps running.
  • Script crashes throw. A bug in your own code (calling a method on undefined, a thrown exception, a missing modal field) aborts the run and is reported to the agent's error channel.

Checking action results

result-checking.js (event: messageCreate)
export async function onMessage(message) {
  if (message.author.bot) return;
  if (message.content !== "!cleanup") return;

  const deleted = await message.channel.bulkDelete(10);

  // On success bulkDelete returns a number. On failure (e.g. missing
  // Manage Messages) it returns { error, code } instead.
  if (typeof deleted !== "number") {
    await message.reply("Could not clean up: " + deleted.error);
    return;
  }

  await message.channel.send("Removed " + deleted + " messages.");
}

The rule of thumb: anything you depend on next, check. Sends you do not depend on can be fire-and-forget (still awaited), since a failed send simply returns an error object.

The error channel

When creating or updating an agent in /agents, set the optional Error Channel. Uncaught exceptions in that agent arrive there as an embed with the agent's name, its event, and the error message. Reports are throttled to one per agent per two minutes, so a crash loop does not flood the channel; fix the first report and trigger the event again.

Error name you may seeMeaning
RateLimitError with QUOTA LIMIT EXCEEDEDThe server hit one of its run quotas (see below). Agents resume when the window refills.
ExecutionTimeoutThe run exceeded the time limit and was terminated.
SandboxCrashThe script crashed the runtime, usually circular or extremely nested data passed to db/llm/Discord calls.
OverloadedThe platform's run queue was full; the run was shed. Try again shortly.
Anything else (TypeError, ...)A plain bug in the agent's code, with the message attached.

The limits every run lives under

LimitFreePremium
Execution time per run3.5 s10 s
Execution time when an LLM provider is connected8 s (5 s + 3 s)15 s (10 s + 5 s)
Agents running in parallel (per server)28
Memory per run20 MiB96 MiB
Runs per second / minute (per server)3 / 400100 / 1,000
Runs per hour / day (per server)650 / 8005,000 / 10,000
Script file size24 KiB128 KiB

Every agent execution counts as one run, whether it does anything or not. An agent that returns on the first line still consumed a run, which is why event choice matters: ten agents on messageCreate burn ten runs per message.

Watch your budget live

/profile shows usage bars for the minute, hour, and day windows of the current server, so you can see how close your agents run to the edge before users notice anything.

Writing well-behaved agents

  • Return early, and first. Put the cheapest checks (author.bot, prefix, channel) at the top; most runs should end within a few lines.
  • Prefer payload data over fetches. message.member.roleIds is already in the payload; guild.members.fetch() is a network round trip.
  • Batch reads. One db.find beats ten db.findOne calls in a loop.
  • Keep loops bounded. You cannot sleep or wait; long loops just eat your 3.5 seconds.
  • Do not pass payload objects into db/llm. Extract the plain fields you need; deep or circular structures can crash the sandbox bridge.
  • Consolidate agents. One messageCreate agent with several commands inside spends one run per message; five separate agents spend five.

Debugging workflow

  1. Reproduce with the error channel set; read the exact exception.
  2. If nothing arrives, the agent probably returned early or the quota is exhausted; check /profile and your filter conditions.
  3. Use temporary debug replies (await message.channel.send(JSON.stringify(value).slice(0, 1900))) to inspect data shapes; remove them after.
  4. Use Download Agent in /agents to be certain which version is live, edit, then Update.

Exercise

Take your Lesson 10 economy agent and harden it: check every db write for .error and reply with a friendly failure message; add a guard that replies try again in a minute when a write fails; and move your cheapest condition to the very first line. Then deliberately break it (call message.member.bogus()) and watch the report arrive in your error channel.