Troubleshooting & FAQ
Common Issues
Platform Won't Start
Symptom: mvn spring-boot:run fails immediately.
Check:
- PostgreSQL is running and reachable at the configured URL
METAONE_JWT_SECRETis set and at least 32 characters long- The database user has
CREATE TABLEpermissions
# Test DB connectivity
psql $SPRING_DATASOURCE_URL -c "SELECT 1"
# Verify env var is set
echo $METAONE_JWT_SECRET | wc -c # must be > 32mvn test Fails on Spring Context Startup
Symptom: Full test suite fails with CapabilityException class not found or CapabilityGatewayFactory initialization error.
Workaround: Run the reliable focused test instead:
mvn -pl metaone-core -Dtest=ExtensionInstallerServiceTest testRoot cause: Missing CapabilityException class in metaone-sdk. Tracked in TODOS P1.
Extension Installation Fails with FAILED Status
Symptom: POST /admin/workspaces/{wsId}/extensions/install returns status: FAILED.
Steps:
- Check the
errorfield in the response — it contains the failure reason - Common causes:
- Plugin JAR not found in
metaone.plugins-path - Signature verification failed (set
metaone.security.skip-signature: truein dev) - Workspace DB provisioning failed (check PostgreSQL permissions)
- Plugin JAR not found in
# Check what's in the plugins directory
ls -la $(find . -name "plugins" -type d | head -1)
# Check installation record error
curl http://localhost:8080/api/catalog/workspaces/{wsId}/extensions/{key}Extension Stuck in PENDING_CONFIG
Symptom: Extension is installed but status: PENDING_CONFIG and enabled: false.
Cause: The extension has required: true config fields that haven't been set.
Fix:
# 1. Get the config schema and see which fields are required
curl http://localhost:8080/api/admin/workspaces/{wsId}/extensions/{key}/config
# 2. Set the required values
curl -X PUT http://localhost:8080/api/admin/workspaces/{wsId}/extensions/{key}/config \
-H "Content-Type: application/json" \
-d '{"fieldKey": "value", ...}'
# 3. Enable the extension
curl -X POST http://localhost:8080/api/admin/workspaces/{wsId}/extensions/{key}/enableCapability Invocation Returns 404
Symptom: POST /workspaces/{wsId}/capabilities/{key} → 404.
Causes:
- Extension is not installed in this workspace
- Capability key is not in the manifest
provideslist - Extension is disabled
Diagnose:
# Check installed extensions
curl http://localhost:8080/api/catalog/workspaces/{wsId}/extensions
# Check registered capabilities
curl http://localhost:8080/api/catalog/workspaces/{wsId}/extensions/{key}/capabilitiesIn-Process Capability Handler Not Found
Symptom: Capability routes to extension but throws No handler found for key: ....
Cause: Handler annotation key doesn't match manifest key.
Check:
- Handler:
@CapabilityGroup(prefix = "crm.product")+@Capability(key = "search")→crm.product.search - Manifest:
CapabilityProvide("crm.product.search", "workspace")
Both must produce the exact same string. Keys are case-sensitive.
Events Not Being Delivered
Symptom: Published events don't reach extension handlers.
Diagnose:
- Verify the extension subscribes to this event key in its manifest (
consumesEvents) - Verify the extension is enabled
- For in-process: verify the handler class has
@Extension+ implementsPlatformEventHandler - For external: check the service is running at
serviceBaseUrl
# Publish a test event and watch logs
curl -X POST http://localhost:8080/api/workspaces/{wsId}/events/publish \
-d '{"eventKey":"crm.deal.closed","payload":{"dealId":"test-123"}}'Look for log lines containing Dispatching event and Delivered to.
ClassCastException in Capability Handler
Symptom: ClassCastException: ... cannot be cast to CapabilityResult.
Cause: Handler method returns a raw type instead of CapabilityResult<T>.
Fix:
// ❌ Wrong
public Map<String, Object> search(Map<String, Object> req, CapabilityContext ctx) {
return Map.of("results", results);
}
// ✅ Correct
public CapabilityResult<List<ProductDto>> search(Map<String, Object> req, CapabilityContext ctx) {
return CapabilityResult.success(results);
}Webhook Returns 401
Symptom: External service webhook to /api/webhooks/{key} returns HTTP 401.
Cause: HMAC signature validation failed in WebhookHandler.handleWebhook().
Check:
- The webhook secret in your extension config matches what the external service uses
- You're computing HMAC over the raw request bytes (not the parsed JSON)
- The signature header name matches what you're reading (headers are lowercased)
JWT Authentication Fails (401 Unauthorized)
Symptom: API requests return 401 after login.
Check:
- The JWT cookie is included in the request (
-b cookies.txtwith curl) - The
METAONE_JWT_SECREThasn't changed between login and the subsequent request - The token hasn't expired (
METAONE_JWT_EXPIRATION_MS, default 24h)
OAuth2 Login Redirect Fails
Symptom: Google OAuth2 login redirects to an error page.
Check: The redirect URI https://your-app/oauth2/callback/google must be:
- Listed in
metaone.security.oauth2.authorized-redirect-uris - Registered in your Google OAuth2 client in Google Cloud Console
FAQ
Q: Can an extension be installed in multiple workspaces?
A: Yes. Each workspace has its own ExtensionInstallation row and its own configuration. Extensions are workspace-isolated.
Q: Do I need to restart the platform to install a new extension?
A: No. Extensions are loaded at install time via PF4J. No restart is required.
Q: How do I debug an in-process extension?
A: Run the platform with remote debugging enabled:
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"Then attach your IDE debugger to port 5005. Breakpoints in plugin code work normally.
Q: Can two extensions share data?
A: Extensions should not directly share database tables. Cross-extension communication happens via events (EventBusGateway.publish()) or capability invocations.
Q: What happens if an event handler throws an exception?
A: For async handlers, the exception is logged. For sync handlers, it propagates to the event publisher. Failed external deliveries go to dead_letter_event.
Q: How do I add a new workspace database?
A: The platform creates workspace databases automatically when a workspace is created, using the metaone.workspace.datasource.admin-url connection. Ensure the DB user has CREATE DATABASE privilege on the admin connection.
Q: Why do I see tenantId in some places and workspaceId in others?
A: Legacy naming. Both refer to the same concept — the workspace. The SDK event models use tenantId; host code uses workspaceId. A cleanup is tracked in TODOS P2.
Q: How do I add IDE support for PF4J plugins in IntelliJ?
A: Add metaone-sdk as a "provided" dependency in the plugin module POM. IntelliJ will resolve the SDK classes. Mark the plugins/ directory as excluded from indexing to avoid conflicts.