tursodatabase/turso · error

invalid MVCC pull revision generation: {revision}

Error message

invalid MVCC pull revision generation: {revision}

What it means

client_revision strings containing ":o" are parsed as g<generation>:o<offset>, and the generation segment must begin with a literal 'g'. Any other prefix — "2:o5", "gen1:o5", "rev:o0" — is rejected as an invalid generation with HTTP 500 before any log data is served.

Source

Thrown at cli/sync_server.rs:925

            "MVCC logical pull is not supported for in-memory sync server databases"
        ));
    }
    let path = if let Some(rest) = db_path.strip_prefix("file:") {
        rest.split_once('?').map_or(rest, |(path, _)| path)
    } else {
        db_path
    };
    Ok(PathBuf::from(path))
}

fn parse_mvcc_revision_offset(revision: &str, legacy_default: u64) -> Result<u64> {
    if revision.is_empty() {
        return Ok(0);
    }
    if let Some((generation, offset)) = revision.split_once(":o") {
        let generation = generation
            .strip_prefix('g')
            .ok_or_else(|| anyhow!("invalid MVCC pull revision generation: {revision}"))?
            .parse::<u64>()
            .map_err(|err| anyhow!("invalid MVCC pull revision generation: {revision}: {err}"))?;
        if generation != 1 {
            return Err(anyhow!(
                "sync_server supports only single-generation MVCC logical pulls: {revision}"
            ));
        }
        return offset
            .parse::<u64>()
            .map_err(|err| anyhow!("invalid MVCC pull revision offset: {revision}: {err}"));
    }
    // Older page bootstrap responses from this test server used WAL frame
    // numbers. Treat them as "the page snapshot already includes the current
    // logical log" so the required follow-up logical pull becomes a no-op.
    Ok(legacy_default)
}

fn scan_mvcc_log(log: &[u8]) -> Result<MvccLogSnapshot> {

View on GitHub (pinned to bad083fafb)

Solutions

  1. Only send revisions the server previously returned (the server_revision field of the last response).
  2. Send an empty client_revision to pull from offset 0.
  3. For page-bootstrapped clients, plain numeric WAL-frame revisions (no ':o') are accepted as the legacy default — use that form.

Example fix

// before
req.client_revision = "1:o128".to_string();

// after
req.client_revision = "g1:o128".to_string();
Defensive patterns

Strategy: validation

Validate before calling

fn is_server_revision(rev: &str) -> bool {
    if rev.is_empty() || rev.chars().all(|c| c.is_ascii_digit()) {
        return true; // empty, or legacy WAL-frame revision
    }
    matches!(
        rev.strip_prefix('g').and_then(|r| r.split_once(":o")),
        Some((g, o)) if g.parse::<u64>().is_ok() && o.parse::<u64>().is_ok()
    )
}

Try / catch

On 500 'invalid MVCC pull revision generation', do not retry the same string: re-read the last server_revision from the previous response, or send an empty client_revision to restart from offset 0.

Prevention

When it happens

Trigger: Hand-built or legacy revision strings sent to /pull-updates with stream_kind=MvccLogicalLog; clients composing the revision grammar themselves instead of reusing server-provided strings.

Common situations: Custom sync clients, ported scripts, and tests that fabricate revision strings; protocol drift where another component emits a different revision format.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/0650f5996071afdc. Report an issue: GitHub.