clockworklabs/SpacetimeDB · error · TypeError

Table defines multiple schedules: and . A schedule table…

Error message

Table ${tableName} defines multiple schedules: ${existingFunctionName} and ${functionName}. A schedule table can only be used by one reducer or procedure.

What it means

The scheduler dispatches one function per schedule table, so `resolveSchedules` keeps a `scheduledTables` map of tableName -> functionName and throws if a second function claims a table already scheduled. The message names both the existing and the new function. One schedule table == one reducer or procedure, enforced at build time.

Solutions

  1. Give the second function its own schedule table (a fresh `table()` handle with its own `scheduleAt` column) and insert into the table matching the desired behavior.
  2. Alternatively, merge both handlers into one scheduled function that switches on the row's content.
  3. Delete the duplicate registration named in the error message if it was left over from a refactor.

Example fix

// before — two functions, one table
export const f1 = spacetimedb.reducer({ onSchedule: tasks }, ...);
export const f2 = spacetimedb.reducer({ onSchedule: tasks }, ...); // TypeError

// after
export const f1 = spacetimedb.reducer({ onSchedule: urgentTasks }, ...);
export const f2 = spacetimedb.reducer({ onSchedule: routineTasks }, ...); // separate tables
Defensive patterns

Strategy: validation

Validate before calling

// Enforce one scheduled function per table in dev tooling.
const scheduleOwners = new Map<object, string>();
function claimScheduleTable(table: object, fnName: string) {
  const prev = scheduleOwners.get(table);
  if (prev !== undefined) throw new Error(`${table} already scheduled by ${prev}; ${fnName} needs its own table`);
  scheduleOwners.set(table, fnName);
}

Prevention

When it happens

Trigger: Two reducers (or a reducer plus a procedure) both using `{ onSchedule: sameTable }`; registering a second scheduled function while iterating a list of tables with a shared handle; adding a new scheduled consumer for an existing schedule table.

Common situations: Splitting a busy scheduled handler into two functions and pointing both at the same table; library code that schedules on 'its' table which the app also schedules on; misunderstanding that the table, not the row type, identifies the schedule.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-08-20). Data as JSON: /api/errors/2baf2d53ac620a67. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-typescript/src/server/schema.ts:194

      const scheduleAtCol = knownScheduleAtCol ?? table.scheduleAtCol;
      if (scheduleAtCol === undefined) {
        throw new TypeError(
          `Table ${tableName} defines a schedule, but it does not have a ScheduleAt column.`
        );
      }

      const functionName =
        knownFunctionName ??
        (reducer === undefined
          ? undefined
          : this.functionExports.get(reducer()));
      if (functionName === undefined) {
        const msg = `Table ${tableName} defines a schedule, but it seems like the associated function was not exported.`;
        throw new TypeError(msg);
      }
      const existingFunctionName = scheduledTables.get(tableName);
      if (existingFunctionName !== undefined) {
        throw new TypeError(
          `Table ${tableName} defines multiple schedules: ${existingFunctionName} and ${functionName}. A schedule table can only be used by one reducer or procedure.`
        );
      }
      scheduledTables.set(tableName, functionName);
      this.moduleDef.schedules.push({
        sourceName: undefined,
        tableName,
        scheduleAtCol,
        functionName,
      });
    }
  }

  resolveHttpRoutes() {
    for (const route of this.pendingHttpRoutes) {
      const handlerFunction = this.httpHandlerExports.get(route.handler);
      if (handlerFunction === undefined) {
        throw new TypeError(

View on GitHub (pinned to eddf9f5014)