Valet

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.

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 { mutate } = useMutation(api.todos.create)

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

    // CORRECT: title is a string
    mutate({ 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 { mutate } = useMutation(api.todos.create)

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

    // CORRECT: passing an object
    mutate({ 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 { mutate } = useMutation(api.counters.increment)

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

    // CORRECT: passing an integer
    mutate({ 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