clockworklabs/SpacetimeDB · error

Database environment variables can only be changed by…

Error message

Database environment variables can only be changed by publishing

What it means

SQL DML against the st_env system table is blocked: environment variables for a database are managed exclusively through the publish flow, which validates keys, records the environment schema, and updates program metadata atomically. Direct SQL writes would bypass those invariants, so run_inner rejects them with this error.

Solutions

  1. Change environment variables by republishing with the desired environment spec (`spacetime publish` with the updated environment).
  2. Read-only SELECTs on st_env remain allowed — use SELECT to inspect current values.
  3. For secrets/config not tied to publishing, store them in your own table instead of st_env.

Example fix

-- before: direct DML on env table
UPDATE st_env SET value = 'v2' WHERE key = 'API_KEY';

-- after: change via publish (CLI)
-- spacetime publish mydb --env API_KEY=v2
Defensive patterns

Strategy: validation

Validate before calling

-- reject st_env DML before sending
if sql.trim().toUpperCase().startsWith(('INSERT','UPDATE','DELETE')) && /\bst_env\b/.test(sql) throw new Error('mutate st_env only via publish');

Try / catch

try { execSql(sql); } catch (e) { if (e.message.includes('can only be changed by publishing')) switchToPublishFlow(); else throw e; }

Prevention

When it happens

Trigger: Executing an SQL INSERT/UPDATE/DELETE statement whose stmt.table_id() == ST_ENV_ID via the SQL execution API, even when the caller has write access.

Common situations: A developer trying to set or tweak a database config variable with `UPDATE st_env SET value=...` in the SQL console; automation scripts mutating env rows directly.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at crates/core/src/sql/execute.rs:150

            // Update transaction metrics
            tx.metrics.merge(metrics);

            Ok((
                SqlResult {
                    tx_offset,
                    rows,
                    metrics: tx.metrics,
                },
                trapped,
            ))
        }
        Statement::DML(stmt) => {
            // An extra layer of auth is required for DML
            if !auth.has_write_access() {
                return Err(anyhow!("Caller {} is not authorized to run SQL DML statements", auth.caller()).into());
            }
            if stmt.table_id() == spacetimedb_datastore::system_tables::ST_ENV_ID {
                return Err(anyhow!("Database environment variables can only be changed by publishing").into());
            }

            // Evaluate the mutation
            let (mut tx, _) = db.with_auto_rollback(tx, |tx| execute_dml_stmt(&auth, stmt, tx, &mut metrics))?;

            // Update transaction metrics
            tx.metrics.merge(metrics);

            // Update views
            let (result, _num_views_evaluated, trapped) = match instance {
                Some(instance) => ModuleHost::call_views_with_tx(tx, instance, auth.caller()),
                None => (ViewCallResult::default(tx), 0, false),
            };

            // Rollback transaction and report metrics if view execution failed
            if let ViewOutcome::Failed(err) = result.outcome {
                let (_, metrics, reducer) = db.rollback_mut_tx(result.tx);
                db.report_mut_tx_metrics(reducer, metrics, None);

View on GitHub (pinned to eddf9f5014)