benbjohnson/litestream · error

litestream_lag is read-only

Error message

litestream_lag is read-only

What it means

The litestream_lag PRAGMA is read-only: FileControl() in vfs.go returns this error when pragmaValue is non-nil, i.e. the caller executed `PRAGMA litestream_lag = <value>`. Reading it returns the seconds since the last successful replica poll ('-1' if never polled successfully).

Source

Thrown at vfs.go:2447

		return nil, fmt.Errorf("unsupported file control op: %d", op)
	}

	name := strings.ToLower(pragmaName)

	f.logger.Debug("file control", "pragma", name, "value", pragmaValue)

	switch name {
	case "litestream_txid":
		if pragmaValue != nil {
			return nil, fmt.Errorf("litestream_txid is read-only")
		}
		txid := f.Pos().TXID
		result := txid.String()
		return &result, nil

	case "litestream_lag":
		if pragmaValue != nil {
			return nil, fmt.Errorf("litestream_lag is read-only")
		}
		lastPoll := f.LastPollSuccess()
		if lastPoll.IsZero() {
			result := "-1" // Never polled successfully
			return &result, nil
		}
		lag := int64(time.Since(lastPoll).Seconds())
		result := strconv.FormatInt(lag, 10)
		return &result, nil

	case "litestream_time":
		if pragmaValue == nil {
			result := f.currentTimeString()
			return &result, nil
		}

		if strings.EqualFold(*pragmaValue, "latest") {
			if err := f.ResetTime(context.Background()); err != nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Remove the assignment; use the read form `PRAGMA litestream_lag;`
  2. Wait for replication to catch up or trigger a replication sync through litestream's API/CLI to lower actual lag
  3. For monitoring, poll the read form and alert based on the returned seconds (-1 means never polled)
  4. Catch the error and explain that lag is a computed metric, not a setting

Example fix

// before
PRAGMA litestream_lag = 0;  -- attempt to reset lag
// after
PRAGMA litestream_lag;  -- read current lag in seconds (-1 = never polled)
// to reduce lag, trigger replication:
//   litestream replicate ... / use SyncAndWait() from the Go API
Defensive patterns

Strategy: validation

Validate before calling

// Reject assignment form before executing
if strings.Contains(strings.ToLower(stmt), "litestream_lag=") ||
   strings.Contains(strings.ToLower(stmt), "litestream_lag =") {
    return errors.New("litestream_lag is read-only; use the read form PRAGMA litestream_lag;")
}

Try / catch

_, err := db.Exec("PRAGMA litestream_lag = ?", v)
if err != nil && strings.Contains(err.Error(), "litestream_lag is read-only") {
    // fall back to reading current lag
    row := db.QueryRow("PRAGMA litestream_lag")
    var lag string
    _ = row.Scan(&lag)
}

Prevention

When it happens

Trigger: Executing `PRAGMA litestream_lag = '0'` or any assignment form, typically in an attempt to zero out or adjust the reported replication lag. The non-nil pragmaValue triggers the read-only guard.

Common situations: Developers assume lag is tunable (e.g. to force immediate sync or silence alerting) and try to assign to it. In reality lag is derived from f.LastPollSuccess(); to reduce actual lag you must let replication catch up or trigger a sync via the litestream API/CLI.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/f0803b013b707a3d. Report an issue: GitHub.