Troubleshooting
Invalid or expired session token
Section titled “Invalid or expired session token”{"error":"Invalid or expired session"}The session JWT could not be verified. This happens when:
- Token has expired. Sessions expire after
tokenLifetime(default 24 hours). Re-authenticate via the Console UI. - Connection token was rotated. After running
npx @usebetterdev/console-cli token rotate, all existing sessions are invalidated because JWTs are signed with the connection token secret. UpdateBETTER_CONSOLE_TOKEN_HASHin your environment, restart your server, and re-authenticate. - Wrong
BETTER_CONSOLE_TOKEN_HASH. Ensure the environment variable matches the hash generated byinitortoken generate.
Auto-approve blocked in production
Section titled “Auto-approve blocked in production”ConsoleAutoApproveInProductionError: autoApprove is enabled outside of development.This error is thrown during betterConsole() construction — your server will not start. Auto-approve is only allowed when NODE_ENV is "development" or "test".
Fix: Either set NODE_ENV=development for local dev, or switch to magic link auth:
const consoleInstance = betterConsole({ adapter: drizzleConsoleAdapter(db), connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!, sessions: { magicLink: { allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!, }, },});Email not in allowed list
Section titled “Email not in allowed list”{"error":"Email \"[email protected]\" is not in the allowed list.","code":"EMAIL_NOT_ALLOWED"}The email address used to initiate a magic link session is not in the allowedEmails configuration.
Fix: Add the email to BETTER_CONSOLE_ALLOWED_EMAILS:
Then restart your server.
Adapter required for magic link
Section titled “Adapter required for magic link”{"error":"Magic link sessions require a database adapter.","code":"ADAPTER_REQUIRED"}Magic link sessions store session and magic link records in the database. You must provide a database adapter.
Fix: Add the Drizzle adapter:
import { drizzleConsoleAdapter } from "@usebetterdev/console/drizzle";
const consoleInstance = betterConsole({ adapter: drizzleConsoleAdapter(db), // add this connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!, sessions: { magicLink: { allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!, }, },});Database tables missing
Section titled “Database tables missing”check: FAIL - table "better_console_sessions" not foundcheck: FAIL - table "better_console_magic_links" not foundThe console tables have not been created in your database.
Fix: Run the migration:
# Option A: Pipe directly to psqlnpx @usebetterdev/console-cli migrate --dry-run | psql $DATABASE_URL
# Option B: Generate a filenpx @usebetterdev/console-cli migrate -o drizzle/console_tables.sql# Then apply via your migration tool or psqlVerify with:
npx @usebetterdev/console-cli check --database-url $DATABASE_URLCORS errors
Section titled “CORS errors”Access to fetch at 'https://myapp.com/.well-known/better/console/capabilities'from origin 'https://console.usebetter.dev' has been blocked by CORS policyThe Console UI origin is not in the allowedOrigins list, or CORS headers are being stripped by a reverse proxy.
Common causes
Section titled “Common causes”Reverse proxy strips headers. If you use nginx, Cloudflare, or another proxy, ensure it forwards CORS headers from your application. Do not add separate CORS headers at the proxy level — let UseBetter Console handle them.
Custom origins not included. If you host the Console UI yourself or access it from a custom domain:
const consoleInstance = betterConsole({ // ... allowedOrigins: [ "https://console.usebetter.dev", // default — keep this "https://admin.myapp.com", // your custom origin ],});Missing middleware. The Console middleware must be mounted before any other middleware that might handle /.well-known/better/* routes:
app.use("*", createConsoleMiddleware(consoleInstance));Session expired
Section titled “Session expired”{"error":"Invalid or expired session"}The database session record has passed its expires_at timestamp. Re-authenticate via the Console UI.
To extend session lifetime, configure tokenLifetime:
sessions: { tokenLifetime: "7d", // max 7 days (default: 24h)},Health endpoint returns 404
Section titled “Health endpoint returns 404”curl http://localhost:3000/.well-known/better/console/health# 404 Not FoundThe Console middleware is not intercepting requests.
Common causes
Section titled “Common causes”Middleware not mounted. Ensure createConsoleMiddleware() is called and applied to your Hono app:
app.use("*", createConsoleMiddleware(consoleInstance));Wrong base path. The middleware intercepts /.well-known/better/* by default. If your app is behind a path prefix (e.g., /api), the full path would be /api/.well-known/better/console/health.
App not running. Verify your server is actually running on the expected port.
Permission denied
Section titled “Permission denied”{"error":"Insufficient permissions"}Your session does not have the required permission level for the endpoint you’re accessing. The permission hierarchy is read < write < admin.
Fix: Check what permission the endpoint requires and ensure your session has at least that level. The allowedActions config determines the maximum permission level granted to sessions:
const consoleInstance = betterConsole({ // ... allowedActions: ["read", "write", "admin"], // default — grants all levels});No session method configured
Section titled “No session method configured”{"error":"No session method configured.","code":"NO_SESSION_METHOD"}You must enable at least one of: autoApprove, magicLink, or authenticate in the sessions config.
Fix:
sessions: { autoApprove: process.env.NODE_ENV === "development", // or // or authenticate: async (request) => { /* ... */ }, // custom auth},Email relay failed
Section titled “Email relay failed”{"error":"Console email relay returned 404: email not registered","code":"EMAIL_RELAY_FAILED"}The Console email relay could not deliver the verification code. Common causes:
- Email not registered in Console. The relay only sends to emails with an existing account at
console.usebetter.dev. Sign up at console.usebetter.dev with the same email address listed inallowedEmails. - Rate limited (429). Too many magic link requests in a short period. Wait a few minutes and try again.
- Network error. Your server could not reach
api.usebetter.dev. Check your firewall and DNS settings.
Fix: If you prefer to bypass the relay entirely, provide your own sendMagicLinkEmail callback:
sessions: { magicLink: { sendMagicLinkEmail: async ({ email, code }) => { await yourEmailService.send({ to: email, body: `Code: ${code}` }); }, },},Weak secret in production
Section titled “Weak secret in production”{"error":"connectionTokenHash secret is too short","code":"WEAK_SECRET"}The connection token hash resolves to a secret shorter than 32 characters. This check is enforced when NODE_ENV is not "development" or "test".
Fix: Generate a new, strong token:
npx @usebetterdev/console-cli token generateUpdate BETTER_CONSOLE_TOKEN_HASH in your environment with the new value.
Next steps
Section titled “Next steps”- Configuration — full config reference
- Authentication — auth methods and session management
- CLI — all CLI commands