Troubleshooting
A reference of common failures, grouped by surface area. Channel-specific issues live in Connect a Channel.
Login
“I cannot log in with Google.” (managed services) The login page reports only that the account cannot sign in, on purpose. Two rules gate a Google sign-in and both have to admit it - check them in this order, and read the panel’s server log, which names the one that refused (federated sign-in refused (…)):
- Not invited. An account with no organization membership and no valid invitation is refused. This is unconditional. Invite them from Members and roles, or create the account and its membership first and have them sign in afterwards - the Google identity links to the account that already exists.
- The organization’s SSO domains. If every organization the account belongs to declares a domain list and none of those lists names the address’s domain, it is refused. An organization with an empty list restricts nothing. A platform admin edits the lists in Admin Console → Organizations, and is itself exempt from both rules.
Both gate the Google provider only - email + password is unaffected, which is the way back in if you have locked out the wrong people. There is no AUTH_ALLOWED_DOMAIN any more: the list is per-organization product configuration.
- Confirm
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETare set inpackages/web/.env.local(Next.js does not read the monorepo-root.env). - Confirm the OAuth redirect URI configured in Google Cloud Console matches the admin panel URL.
“The Google sign-in button is missing on /login.” (managed services)
GOOGLE_CLIENT_IDand/orGOOGLE_CLIENT_SECRETare unset. The provider auto-disables itself with a stderr warning log when either is missing. Set both and restart the web package.
“There is no admin account - I never set INITIAL_ADMIN_PASSWORD.”
- Without
INITIAL_ADMIN_PASSWORDno admin is seeded at all. The engine does not generate a password: it logs that it skipped the seed, so a secret never lands in its own log. - On an empty
userstable, which is the usual state when no admin was ever seeded, setINITIAL_ADMIN_PASSWORD(and optionallyINITIAL_ADMIN_EMAIL) and restart the engine. This works on both builds. - On a table that already holds users, the builds differ. Open-source build: the seeder does nothing once any user exists, so it neither creates nor promotes the account you name; recovering means clearing the
userstable, or promoting an account that can already sign in withUPDATE users SET is_platform_admin = true WHERE email = '…';. Managed-services build: you do not have to clear the table. SetINITIAL_ADMIN_EMAILandINITIAL_ADMIN_PASSWORDand restart: the seeder creates that user if it is absent, and if the user exists without a password it sets the one you supplied and promotes them to platform admin. It never overwrites a password that has already been set, so this is not a way back into an account whose password was rotated.
“Credentials (email/password) login always returns 401.”
AUTH_INTERNAL_SECRETis missing, or its value differs betweenpackages/web/.env.localand the engine.env. The web package signs every credentials-verification request with it, and the engine rejects mismatches. Set the same value on both sides and restart.
“I can log in, but I am bounced back to /login on every page navigation.”
AUTH_SECRETis different inpackages/web/.env.localvs. the engine’s.env. They must match exactly.- Cookie domain mismatch behind a reverse proxy. Set
AUTH_TRUST_HOST=true.
“My session expired without warning.”
- JWT lifetime defaults to 24 hours. The token cannot be revoked early. Re-login.
“Invitations and password-reset mails stopped arriving after the upgrade.” (managed services)
- The mail relay is no longer read from
SMTP_*,MAIL_FROMandPANEL_URL, and those values are not a fallback. Until a platform admin enters the relay at Admin Console → Installation policies → Outbound mail, the installation has no mailer: invitations are created but not delivered, and reset requests send nothing. See Installation policies.
Configuration
“The engine logs ... is set but no longer read at boot.”
- The variable was retired and the value has no effect. The warning names where the value is set now; enter it there if it differed from the default, then remove the variable. The full list is in Retired variables.
“I changed a limit or timeout in the environment and nothing happened.”
- Stream caps, rate limits, agent-to-agent and MCP timeouts and scheduler deadlines are Installation policies now, edited in the panel. The environment variables with those names are read by nothing.
Channels
“Telegram or Slack went silent after the upgrade.”
- Both now receive messages through webhooks, so the engine needs a public HTTPS address (
BASE_URL, or the public address in Installation policies) and a proxy that sends/webhooks/*to the engine with no sign-in in front. See the reverse proxy checklist. - Slack also needs Socket Mode switched off and the Event Subscriptions Request URL set to
<public address>/webhooks/slack/<agent slug>. Telegram registers its own webhook when the channel starts.
“Telegram bot does not reply.”
- Bot token wrong, the engine’s public address is not public HTTPS, or Telegram’s webhook does not point to this agent. Use the channel’s Test control to compare the registered URL.
- Allowed-user-id whitelist excludes the tester. Try with the field cleared.
“Slack: no messages arrive.”
- Socket Mode is enabled, the Events API Request URL is not reachable, or
app_mention/message.imsubscriptions are missing. - Missing scope: read the engine error message and add the listed scope on the Slack side.
“WhatsApp: nothing happens.”
- Webhook URL not reachable from Twilio. Test with
curlfrom the public internet. - Sandbox session expired (72 h of inactivity). Send
join <code>again.
“WhatsApp: Twilio reports 404 on the webhook.”
- The URL must be
https://<host>/webhooks/twilio/<instance-slug>/whatsapp. The legacy/intuitive form/webhooks/whatsapp/<slug>is not registered and Twilio will show a 404 in its console. - Confirm the instance slug in the URL matches the actual slug (admin panel → Instances → General).
“Channels won’t start at boot.”
- Channel startup is fire-and-forget:
startAllForInstance()is not awaited at boot. A failed credential check or Telegram webhook registration does not stop the rest of the boot sequence; check the channel’s disabled reason and engine logs. - Check the engine logs for
channelManagerwarnings around startup time. If you don’t see a “started” line for the expected channel within ~10 s of boot, the channel is stuck. - Workaround: restart the channel from Admin Panel → Channels → Restart. That path runs synchronously and surfaces the error.
Database
“pgvector extension not found.”
- You are not running the docker-compose Postgres image. Use
pgvector/pgvector:pg16(or install the extension in your existing Postgres).
“Migrations fail with permission denied on extension creation.”
- Run the very first migration as a Postgres superuser. Subsequent migrations work as a regular user.
“drizzle-kit generate fails with an ESM resolution error.”
- The bare CLI is broken in this repo because of an ESM/CJS interop bug in
drizzle-kititself. Use the wrapper script:npm run db:generate -w @polyant/engine. It invokestsx ../../node_modules/drizzle-kit/bin.cjs generate, which works around the issue. - No snapshot files are committed, so every
generateproduces a full-schema migration. For schema changes, write incremental migrations by hand inpackages/engine/src/database/migrations/rather than committing the auto-generated full dump.
Tools
“My tool doesn’t appear on the agent.” Walk down the list in order - each step is a separate failure mode:
- File name & path. The file must be at
packages/engine/src/agents/tools/*.tool.ts. The.tool.tssuffix is required;loadAllTools()filters by it. - Registered name. The
namepassed todefineTool({ name: "..." })is case-sensitive and must match the name the supervisor expects. Typos here produce silent no-ops. Check the export shape too: a*.tool.tsfile that does not default-export adefineToolresult is skipped at boot with a console warning, so the tool never exists at all. - Per-instance enablement. Even a registered tool is only available to instances that have a row in
instance_toolsenabling it. Check via Admin Panel → Instance → Tools orSELECT * FROM instance_tools WHERE instance_id = .... - Skill remapping. Toggling skills on/off triggers
recomputeInstanceToolsautomatically, which may have disabled a tool the skill no longer requires. Re-enable it manually in the Tools tab. - Required secret missing. A tool that declares a required secret is silently not served while the agent (or, with managed services, its organization) lacks it. Open the tool from the agent’s Tools tab and set the key there.
- Old flat name. The GitHub, Render, HubSpot, Markdown-to-PDF and file-upload tools are plugins with namespaced names (
github:issue,hubspot:contact,extra:markdownToPdf,extra:fileUpload). The plugin must be in the image, granted to the organization and assigned to the workspace, and the namespaced tool enabled on the agent. See Upgrading.
“OpenAI strict-mode rejected my tool schema.”
- OpenAI strict mode imposes additional Zod constraints (no
.optional()without.nullable(), no unions of incompatible primitives, no top-level.union()with.literal()mixed). See the schema-authoring rules in concepts/tools.md. - Quick test: run the tool against Anthropic (no strict mode) first. If it works there but fails on OpenAI, it’s a strict-mode issue.
Activity feed
“The Activity feed is empty / silent.”
- Usually means the instance has no traffic, not that anything is broken. The bus is bounded in-memory (last 100 events). Send a test message to verify.
- The live indicator in the top-right distinguishes a connection problem (grey) from a quiet system (green). If grey, the SSE connection dropped - the page auto-reconnects, give it a few seconds.
- The feed does not persist across engine restarts. After a restart the buffer starts empty until new events arrive. For historical data use Conversations or Audit Logs.
- Filter state is in
localStorage. If you only see “system” events and not instance-scoped ones, check the instance filter (top-right of the page) - you may have hidden them in a previous session.
Provider
“Memory extraction throws every time.”
- The agent has memory enabled but no key for its embedder. Memory is embedded by the embedder chosen in the agent’s Model → Embeddings task, separately from the chat provider: add that provider’s key (an OpenAI key for the default embedder) even if your chat provider is Anthropic. With managed services, bind a connection for the embedder’s provider to the agent’s
embeddingrole in its Providers section; the memory banner and the Status section give the reason a bound one cannot run.
“/v1/chat/completions returns 401 Invalid API key.”
- The instance has
authEnabled = truebut you sent the wrong bearer. The auth API key is the one configured in Channels → Web/API → API Key, not your provider key.
“/v1/chat/completions or chat/stream returns 404 for an agent that exists.”
- The agent’s Web channel is off. Since 1.2.0 a new agent starts with it off, and both endpoints answer
404until it is switched on in the agent’s Channels → Web/API card, which needs the agent’s API key unless authentication is turned off. Scripts that create an agent and then call it must save its key and enable the channel (PUT /api/instances/<slug>/channels/webwith{"config":{},"enabled":true}). The panel’s Playground does not need the channel. See Channels → Web/API.