ValetAlpha

Error Reference

Every Valet error message with its cause and fix.

// App.tsx
import { ValetProvider } from './_generated/valet/react'

function App() {
    return (
        <ValetProvider
            url="https://your-project.fly.valet.host"
            onError={(error) => console.error('Valet error:', error)}
        >
            <MyApp />
        </ValetProvider>
    )
}

This page lists every error message Valet produces, organized by category. Each entry includes the exact error string, what causes it, and how to fix it.

Sync Error Codes

These error codes are returned by the server in SyncError and ErrorMessage protocol messages. Each error includes a code, message, suggestion, and docs link.

VERSION_CONFLICT

Code: VERSION_CONFLICT

The document was modified by another client between when you read it and when you tried to write it. This is optimistic locking in action.

Fix: Refetch the document and retry your mutation. If using optimistic updates, the client will automatically rebase pending mutations.

NOT_FOUND

Code: NOT_FOUND

The document you tried to update or delete does not exist.

Fix: Check that the document ID is correct and the document has not been deleted.

ALREADY_EXISTS

Code: ALREADY_EXISTS

You tried to insert a document with an _id that already exists in the table.

Fix: Use an update operation instead, or generate a unique ID for the new document.

VALIDATION_ERROR

Code: VALIDATION_ERROR

The arguments passed to a mutation did not pass validation. The field property on the error indicates which argument failed.

Fix: Check that the arguments match the validators defined in your mutation.

FUNCTION_NOT_FOUND

Code: FUNCTION_NOT_FOUND

The mutation function name does not match any registered function on the server.

Fix: Verify the function name matches a registered mutation. Run bunx valet-dev codegen to ensure your schema is up to date.

HANDLER_ERROR

Code: HANDLER_ERROR

Your mutation handler threw an exception during execution. The error message contains the exception details.

Fix: Check your mutation handler for bugs. The error message contains the exception thrown by your code.

UNAUTHORIZED

Code: UNAUTHORIZED

The operation requires authentication and either no token was provided or the token is invalid.

Fix: Check that your auth token is valid and has not expired. If using JWT, verify the token's claims match your server configuration.

AUTH_REQUIRED

Code: AUTH_REQUIRED

The table has row-level security that requires authentication, but the client did not provide an auth token.

Fix: Provide a valid auth token when connecting. See the auth guide for details.

SUBSCRIPTION_NOT_FOUND

Code: SUBSCRIPTION_NOT_FOUND

The subscription ID does not match any active subscription on the server.

Fix: Re-subscribe to the query. This can happen after a reconnection if the server has evicted old subscriptions.

SYNC_WRITE_FAILED

Code: SYNC_WRITE_FAILED

A catch-all error for sync write failures not covered by more specific codes.

Fix: Check the server logs for details. This is usually a transient database error.

INVALID_DOC

Code: INVALID_DOC

The document in the sync write message is not valid. Insert and update require a JSON object, and update/delete require an _id field.

Fix: Ensure the document is a valid JSON object with the required fields for the operation.

RLS_VIOLATION

Code: RLS_VIOLATION

The document does not satisfy the table's row-level security filter for your user context. You can only read and write documents that your sync filter allows.

Fix: Verify that the document's fields satisfy the sync filter for the authenticated user.

INVALID_OPERATION

Code: INVALID_OPERATION

The operation field in the sync write message is not one of the supported values.

Fix: Use one of: insert, update, delete.

INVALID_FILTER

Code: INVALID_FILTER

The subscription filter expression could not be parsed.

Fix: Check your filter expression syntax. Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $and, $or, $not.

DATABASE_ERROR

Code: DATABASE_ERROR

An internal database error occurred. This is usually transient.

Fix: Retry the operation. If the problem persists, check the server logs.

INVALID_MESSAGE

Code: INVALID_MESSAGE

The WebSocket message could not be parsed as valid JSON or does not conform to the expected protocol format.

Fix: Check that the message is valid JSON conforming to the Valet WebSocket protocol.

RATE_LIMITED

Code: RATE_LIMITED

You are sending messages too fast. The server enforces a per-connection rate limit.

Fix: Reduce the frequency of messages. Consider batching operations.

AUTH_TABLE_PROTECTED

Code: AUTH_TABLE_PROTECTED

You tried to write to an internal auth table (prefixed with _auth_) through the sync protocol.

Fix: Auth tables are managed internally. Use the /auth/* REST endpoints instead.

QUERY_ERROR

Code: QUERY_ERROR

A database query failed during subscription processing.

Fix: Check the server logs for details. This may indicate a database issue or an invalid query.

Schema Errors

"Windowed sync mode requires a 'window' configuration"

// valet/schema.ts -- BROKEN
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    messages: defineTable({
        body: v.string(),
        sentAt: v.number(),
    })
        .sync({
            filter: (q, ctx) => q.gte('sentAt', 0),
            mode: 'windowed',
            // Missing 'window' config
        }),
})

You set mode: 'windowed' without providing a window configuration. Windowed sync mode requires a window object specifying which field to use as the time axis and how large the window is.

Fix: add the window field to the sync configuration.

// valet/schema.ts -- FIXED
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    messages: defineTable({
        body: v.string(),
        sentAt: v.number(),
    })
        .sync({
            filter: (q, ctx) => q.gte('sentAt', 0),
            mode: 'windowed',
            window: { field: 'sentAt', duration: 7 * 24 * 60 * 60 * 1000 },
        }),
})

"Sync mode 'none' should not have a filter"

// valet/schema.ts -- BROKEN
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    analytics: defineTable({
        event: v.string(),
        timestamp: v.number(),
    })
        .sync({
            filter: (q, ctx) => q.gte('timestamp', 0),
            mode: 'none',
        }),
})

You provided a sync filter on a table with sync mode 'none'. Tables with mode: 'none' are server-only and never sync to clients, so a filter has no effect.

Fix: remove the filter function.

// valet/schema.ts -- FIXED
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    analytics: defineTable({
        event: v.string(),
        timestamp: v.number(),
    })
        .sync({ mode: 'none' }),
})

"Sync mode 'X' requires a filter function"

// valet/schema.ts -- BROKEN
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        userId: v.id('users'),
    })
        .sync({
            mode: 'full',
            // Missing 'filter'
        }),
})

You set mode: 'full' or mode: 'windowed' without providing a filter function. These sync modes require a filter to determine which documents sync to each client.

Fix: add a filter function to the sync configuration.

// valet/schema.ts -- FIXED
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        userId: v.id('users'),
    })
        .sync({
            filter: (q, ctx) => q.eq('userId', ctx.auth.userId),
            mode: 'full',
        }),
})

Validation Errors

"Expected string at X, got Y"

Error: Expected string at "args.title", got number (123)

A validator type mismatch. The value passed for a field does not match the declared validator type. This error follows the pattern Expected <type> at "<path>", got <actual> (<value>) for all validator types:

ErrorCause
Expected string at "args.title", got number (123)Field declared as v.string() but received a non-string value.
Expected number at "args.count", got string ("abc")Field declared as v.number() but received a non-number value.
Expected boolean at "args.active", got number (1)Field declared as v.boolean() but received a non-boolean value.
Expected null at "args.value", got string ("null")Field declared as v.null() but received a non-null value.

Fix: pass the correct type for the field.

// components/CreateTodo.tsx
import { useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'

function CreateTodo() {
    const createTodo = useMutation(api.todos.create)

    // WRONG: title is a number
    // createTodo({ title: 123 })

    // CORRECT: title is a string
    createTodo({ title: 'Buy groceries' })
}

"Unexpected argument(s) for X: Y. Valid arguments are: Z"

Error: Unexpected argument(s) for "todos.create": "priority". Valid arguments are: "title"

You passed arguments that are not declared in the function's args validator. The error lists the unexpected fields and the valid fields.

Fix: remove the unexpected arguments, or add validators for them in the function definition.

// valet/todos.ts -- add the missing validator
import { defineMutation, v } from './_generated/valet/api'

export const create = defineMutation({
    args: {
        title: v.string(),
        priority: v.optional(v.number()),  // Add the missing field
    },
    handler: async (ctx, args) => {
        return ctx.db.insert('todos', {
            title: args.title,
            completed: false,
            priority: args.priority ?? 0,
        })
    },
})

"Invalid arguments for X: expected an object, got Y"

Error: Invalid arguments for todos.create: expected an object, got string

You passed a non-object value as the arguments to a query or mutation. Arguments must always be an object (even {} for no arguments).

Fix: wrap your arguments in an object.

// components/CreateTodo.tsx
import { useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'

function CreateTodo() {
    const createTodo = useMutation(api.todos.create)

    // WRONG: passing a string directly
    // createTodo('Buy groceries')

    // CORRECT: passing an object
    createTodo({ title: 'Buy groceries' })
}

"Expected integer at X, got float"

Error: Expected integer at args.count, got float

A field declared with v.int() received a floating-point number. The v.int() validator requires a whole number with no fractional component.

Fix: pass an integer value, or use Math.floor() / Math.round() to convert.

// components/Counter.tsx
import { useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'

function Counter() {
    const increment = useMutation(api.counters.increment)

    // WRONG: passing a float
    // increment({ count: 3.14 })

    // CORRECT: passing an integer
    increment({ count: 3 })
}

Query Errors

'Query on table "X" returned no results, expected exactly one'

Error: Query on table "todos" returned no results, expected exactly one

You called .unique() on a query that matched zero documents. The .unique() terminal method expects the query to return exactly one result.

Fix: use .first() instead if the document might not exist, or verify that the filter matches a document before calling .unique().

// valet/todos.ts
import { defineQuery, v } from './_generated/valet/api'

// Use .first() when the document might not exist
export const findByTitle = defineQuery({
    args: { title: v.string() },
    execution: 'local',
    handler: async (ctx, args) => {
        return ctx.db.query('todos')
            .filter((q) => q.eq('title', args.title))
            .first()  // Returns null instead of throwing
    },
})

'Query on table "X" returned multiple results, expected exactly one'

Error: Query on table "todos" returned multiple results, expected exactly one

You called .unique() on a query that matched more than one document. The .unique() terminal method expects the query to return exactly one result.

Fix: add more specific filters to narrow the result to a single document, or use .first() if you only need one of the matches.

// valet/todos.ts
import { defineQuery, v } from './_generated/valet/api'

// Use .first() if you only need one result
export const firstActive = defineQuery({
    args: {},
    execution: 'local',
    handler: async (ctx) => {
        return ctx.db.query('todos')
            .filter((q) => q.eq('completed', false))
            .first()  // Returns the first match instead of throwing
    },
})

// Or filter more specifically for .unique()
export const getById = defineQuery({
    args: { id: v.id('todos') },
    execution: 'local',
    handler: async (ctx, args) => {
        return ctx.db.query('todos')
            .filter((q) => q.eq('_id', args.id))
            .unique()  // Safe because _id is unique
    },
})

"Query timeout"

Error: Query timeout

A fetchQuery call did not resolve within 30 seconds. This happens when the SyncEngine has not finished initializing, the server is unreachable, or the query takes too long to execute.

Fix: check that the SyncEngine is connected before calling fetchQuery, or increase the timeout if you are running a particularly large query. Verify the server is reachable.

// lib/prefetch.ts
import { fetchQuery } from 'valet-dev/react'
import { api } from './_generated/valet/api'
import type { ValetClient } from 'valet-dev/react'

async function safeFetch(client: ValetClient) {
    try {
        const todos = await fetchQuery(client, api.todos.list, {})
        return todos
    } catch (error) {
        if (error.message === 'Query timeout') {
            console.error('Query timed out -- is the server reachable?')
            return []
        }
        throw error
    }
}

Connection Errors

"useValetClient must be used within a ValetProvider"

Error: useValetClient must be used within a ValetProvider

You called useValetClient() in a component that is not a descendant of ValetProvider. All Valet hooks require a ValetProvider ancestor in the component tree.

Fix: wrap your component tree in ValetProvider.

// App.tsx
import { ValetProvider } from './_generated/valet/react'

function App() {
    return (
        <ValetProvider url="https://your-project.fly.valet.host">
            <MyComponent />  {/* Valet hooks work here */}
        </ValetProvider>
    )
}

"useConnectionState must be used within a ValetProvider"

Error: useConnectionState must be used within a ValetProvider

You called useConnectionState() in a component that is not a descendant of ValetProvider. This error applies to all Valet hooks: useQuery, useMutation, useConnectionState, useIsConnected, useIsReconnecting, usePendingMutationCount, useOfflineMode, and useValetAuth.

Fix: move the component inside the ValetProvider tree.

// App.tsx
import { ValetProvider } from './_generated/valet/react'
import { StatusBar } from './components/StatusBar'

function App() {
    return (
        <ValetProvider url="https://your-project.fly.valet.host">
            <StatusBar />  {/* useConnectionState works here */}
            <MyApp />
        </ValetProvider>
    )
}

CLI Errors

"schema.ts not found"

Error: schema.ts not found in ./valet

The CLI cannot find schema.ts in the valet directory. This happens when:

  • The valet directory does not exist.
  • The valet directory exists but has no schema.ts file.
  • The --valet-dir flag points to the wrong directory.

Fix: verify the valet directory and create schema.ts if missing.

# terminal
bunx valet-dev init

Or, if you have a custom directory:

# terminal
bunx valet-dev codegen --valet-dir src/valet

"Could not reach server"

Error: Could not reach server at https://fly.valet.host

The server URL is unreachable. Codegen still generates local files; only the server push fails.

Causes:

  • The server is down.
  • The URL is incorrect.
  • A network issue (firewall, DNS, VPN).

Fix: check the URL and verify the server is running.

# terminal
curl -s -o /dev/null -w "%{http_code}" https://fly.valet.host/health

If the server is not yet running, codegen works without a server -- you just lose server push:

# terminal
bunx valet-dev codegen --valet-dir valet --output-dir _generated/valet

"401 Unauthorized"

Error: 401 Unauthorized

The deploy key is missing or invalid. The server rejected the push because the request is not authenticated.

Causes:

  • No VALET_DEPLOY_KEY environment variable set.
  • The deploy key has been rotated or revoked.
  • The .env file is missing or has the wrong key.

Fix: set the correct deploy key.

# terminal
bunx valet-dev codegen --deploy-key your-deploy-key

Or set it in your .env file:

# .env
VALET_PROJECT_URL=https://your-project.fly.valet.host
VALET_DEPLOY_KEY=your-deploy-key

"409 Conflict"

Error: 409 Conflict — breaking schema changes detected

You attempted a breaking schema change that the server cannot apply automatically. Breaking changes include:

  • Changing a column's type (e.g., v.number() to v.boolean()).
  • Removing a column that has data.

Fix: instead of modifying an existing column, add a new column with the desired type, backfill it, and deprecate the old column.

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        // Old column: deprecated, stays in the database
        completed: v.number().deprecated(),
        // New column: backfilled from the old value
        isCompleted: v.boolean(),
    })
        .backfill('isCompleted', (doc) => doc.completed === 1),
})

Then run codegen to push the non-breaking migration:

# terminal
bunx valet-dev codegen

On this page