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
- 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.
- Alternatively, merge both handlers into one scheduled function that switches on the row's content.
- 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
- Create a fresh `table()` handle (with `t.scheduleAt()`) for every scheduled function.
- If behaviors must share a queue, use one table and one function that dispatches on row content.
- Document each schedule table's owner next to its definition.
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
- Schedule target table is not part of this schema.
- Schedule target table is registered more than once in this…
- Table defines a schedule, but it does not have a ScheduleAt…
- Table defines a schedule, but it seems like the associated…
- invalid sequence type
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)