Skip to content

Troubleshooting

{"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. Update BETTER_CONSOLE_TOKEN_HASH in your environment, restart your server, and re-authenticate.
  • Wrong BETTER_CONSOLE_TOKEN_HASH. Ensure the environment variable matches the hash generated by init or token generate.

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!,
},
},
});

{"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:

.env
BETTER_CONSOLE_ALLOWED_EMAILS=[email protected],[email protected]

Then restart your server.


{"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!,
},
},
});

check: FAIL - table "better_console_sessions" not found
check: FAIL - table "better_console_magic_links" not found

The console tables have not been created in your database.

Fix: Run the migration:

Terminal window
# Option A: Pipe directly to psql
npx @usebetterdev/console-cli migrate --dry-run | psql $DATABASE_URL
# Option B: Generate a file
npx @usebetterdev/console-cli migrate -o drizzle/console_tables.sql
# Then apply via your migration tool or psql

Verify with:

Terminal window
npx @usebetterdev/console-cli check --database-url $DATABASE_URL

Access to fetch at 'https://myapp.com/.well-known/better/console/capabilities'
from origin 'https://console.usebetter.dev' has been blocked by CORS policy

The Console UI origin is not in the allowedOrigins list, or CORS headers are being stripped by a reverse proxy.

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));

{"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: {
magicLink: { allowedEmails: "[email protected]" },
tokenLifetime: "7d", // max 7 days (default: 24h)
},

Terminal window
curl http://localhost:3000/.well-known/better/console/health
# 404 Not Found

The Console middleware is not intercepting requests.

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.


{"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
});

{"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
magicLink: { allowedEmails: "[email protected]" }, // for production
// or
authenticate: async (request) => { /* ... */ }, // custom auth
},

{"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 in allowedEmails.
  • 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: {
allowedEmails: "[email protected]",
sendMagicLinkEmail: async ({ email, code }) => {
await yourEmailService.send({ to: email, body: `Code: ${code}` });
},
},
},

{"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:

Terminal window
npx @usebetterdev/console-cli token generate

Update BETTER_CONSOLE_TOKEN_HASH in your environment with the new value.