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_CONFLICTThe 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_FOUNDThe 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_EXISTSYou 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_ERRORThe 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_FOUNDThe 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_ERRORYour 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: UNAUTHORIZEDThe 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_REQUIREDThe 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_FOUNDThe 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_FAILEDA 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_DOCThe 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_VIOLATIONThe 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_OPERATIONThe 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_FILTERThe 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_ERRORAn internal database error occurred. This is usually transient.
Fix: Retry the operation. If the problem persists, check the server logs.
INVALID_MESSAGE
Code: INVALID_MESSAGEThe 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_LIMITEDYou 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_PROTECTEDYou 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_ERRORA 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:
| Error | Cause |
|---|---|
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 stringYou 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 floatA 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 oneYou 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 oneYou 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 timeoutA 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 ValetProviderYou 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 ValetProviderYou 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 ./valetThe 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.tsfile. - The
--valet-dirflag points to the wrong directory.
Fix: verify the valet directory and create schema.ts if missing.
# terminal
bunx valet-dev initOr, 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.hostThe 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/healthIf 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 UnauthorizedThe deploy key is missing or invalid. The server rejected the push because the request is not authenticated.
Causes:
- No
VALET_DEPLOY_KEYenvironment variable set. - The deploy key has been rotated or revoked.
- The
.envfile is missing or has the wrong key.
Fix: set the correct deploy key.
# terminal
bunx valet-dev codegen --deploy-key your-deploy-keyOr 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 detectedYou attempted a breaking schema change that the server cannot apply automatically. Breaking changes include:
- Changing a column's type (e.g.,
v.number()tov.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