tursodatabase/turso · error · InvalidOperationException

Batch must contain at least one command.

Error message

Batch must contain at least one command.

What it means

TursoRemoteClient.ExecuteBatchAsync rejects an empty command list with this InvalidOperationException before building the remote pipeline request. An empty batch would produce a request with zero steps and no meaningful result, so it is refused up front rather than sent to the server. Note the separate ArgumentNullException for a null list — this variant is specifically for Count == 0.

Source

Thrown at bindings/dotnet/src/Turso.Data/TursoRemoteClient.cs:85

        if (closeAfter)
            request.Requests.Add(RemoteStreamRequest.Close());

        var response = await SendPipelineAsync(request, commandTimeout, cancellationToken).ConfigureAwait(false);
        UpdateSession(response, closeAfter);
        return ExtractExecuteResult(response);
    }

    public async Task<IReadOnlyList<RemoteStatementResult>> ExecuteBatchAsync(
        IReadOnlyList<TursoBatchCommand> commands,
        int commandTimeout,
        bool wantRows,
        bool closeAfter,
        CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(commands);
        if (commands.Count == 0)
            throw new InvalidOperationException("Batch must contain at least one command.");

        var steps = new List<RemoteBatchStep>(commands.Count);
        foreach (var command in commands)
            steps.Add(new RemoteBatchStep { Statement = BuildStatement(command.CommandText, command.Parameters, wantRows) });

        var request = new RemotePipelineRequest
        {
            Baton = _baton,
            Requests =
            [
                RemoteStreamRequest.Batch(new RemoteBatch { Steps = steps }),
            ],
        };

        if (closeAfter)
            request.Requests.Add(RemoteStreamRequest.Close());

        var response = await SendPipelineAsync(request, commandTimeout, cancellationToken).ConfigureAwait(false);

View on GitHub (pinned to 6c72522679)

Solutions

  1. Guard the call: skip ExecuteBatchAsync when commands.Count == 0 and treat it as a no-op.
  2. Log or assert when a caller builds an empty batch if emptiness indicates an upstream bug.
  3. If batching user input, validate the request body rejects empty operation lists at the API boundary.

Example fix

// before
await client.ExecuteBatchAsync(batch, timeout, wantRows, closeAfter, ct);

// after
if (batch.Count == 0) return [];
await client.ExecuteBatchAsync(batch, timeout, wantRows, closeAfter, ct);
Defensive patterns

Strategy: validation

Validate before calling

if (commands is null || commands.Count == 0)
    return Array.Empty<RemoteStatementResult>(); // empty batch is a no-op

Prevention

When it happens

Trigger: Calling ExecuteBatchAsync with an empty List<TursoBatchCommand>: a batch builder loop that filters out all statements, an API endpoint that accepts a list of operations and received none, or default/empty initialization followed by no Add calls.

Common situations: Bulk-sync endpoints where the incoming change set is empty; conditional batch construction ('if (condition) batch.Add(...)') where no condition matched; unit tests constructing batches programmatically.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/133209b6ce7e54ea. Report an issue: GitHub.