Troubleshooting
The five issues users and plugin authors hit most often. Each is laid out as symptom → cause → fix.
Port conflict on 24056
Symptom. The backend fails to bind 24056 (another process owns it), or two instances collide.
Cause. --port defaults to 24056. When the port is already taken, the launcher falls back to an OS-assigned port and announces it on stdout as FENGYU_PORT=<n> — but a client that hardcoded 24056 will now talk to the wrong process (or nothing).
Fix.
- Read the actual port from the launcher's stdout
FENGYU_PORT=line. The desktop shell and any external supervisor parse this line to discover the port — do the same. - To opt out of the fixed port entirely, launch with
--port=0and always read the announcedFENGYU_PORT. - Free a stuck port: find the holding process (
lsof -i :24056on macOS/Linux) and stop it before relaunching.
See Backend — Port announcement.
Database connection failure
Symptom. The backend starts but falls back into the first-launch wizard even though datasource.properties exists; or POST /api/setup/initialize reports the DB is unreachable.
Cause. At startup the launcher probes the configured database with a JDBC SELECT 1 (5-second login timeout). If the probe fails, it backs the existing config up to a .bak sibling and re-enters SETUP mode so you can supply corrected parameters.
Fix.
- Re-run the wizard:
GET /api/setup/status,GET /api/setup/types, thenPOST /api/setup/test-connectionwith{type, params}to validate without persisting. - Once the test passes,
POST /api/setup/initializeto persist and restart into APP mode. - Your previous config is safe — look for
datasource.properties.baknext to the live config. - For external DBs (
MYSQL,POSTGRESQL), confirm the server is reachable, credentials are correct, and the JDBC URL points at the right host. For embedded backends (H2,SQLITE), confirm the file path is writable.
See Database — Unreachable database and Backend — SETUP vs APP mode.
Token mismatch (401 / 403 everywhere)
Symptom. Every authenticated request returns 401 or 403, but /api/health works.
Cause. The X-FengYu-Token header the client sends does not match the value the launcher was given via --token. The token bypass list (/api/health, /api/setup/*, /plugin-runtime/{id}/**) keeps working, which is why health still responds.
Fix.
- Confirm the token the launcher was started with: it is stored as the system property
fengyu.auth.token, derived from--token=<t>. - Send the same value verbatim as the
X-FengYu-Tokenheader on every request — including the SSE streams (?streamId=/?runId=carry the stream id, never the token). - If you don't know the token, restart the backend with a fresh
--tokenand update all clients.
See REST API — Authentication.
Plugin worker crash
Symptom. A plugin's client.invoke(...) rejects, the host reports the worker exited non-zero, or calls start timing out.
Cause. The worker is a separate OS process (backend/worker.jar) speaking newline-delimited JSON-RPC 2.0 over stdio. It can crash from an unhandled exception, an out-of-memory, or — most commonly — a log line written to stdout that desynchronizes the RPC framing.
Fix.
- Check stderr. Worker logs go to
stderr(the SDK redirectsSystem.outtoSystem.errto protect the protocol channel). The crash reason is there. - Check JSON-RPC framing.
stdoutis reserved for protocol messages — one JSON-RPC object per line. Anything else onstdout(a strayprintln, a banner, a stack trace) corrupts the stream. Keep all diagnostics onstderr. - Restart the worker by disabling and re-enabling the plugin:
PATCH /api/plugin-market/{id}/enabled {enabled:false}then{enabled:true}. Disabling tears the process down; enabling spawns it lazily on next invoke. - For a worker that hangs rather than crashes, cancel any in-flight RPC and disable the plugin to reclaim the process.
See Worker (JSON-RPC) and Pitfalls — Logging to stdout.
Micro-frontend load errors
Symptom. The plugin UI iframe is blank, scripts silently fail to run, or the UI never adopts the host theme.
Cause. Two distinct mechanisms:
- CSP. The host serves plugin UI assets under a strict Content Security Policy. Inline scripts and disallowed origins are refused — they never execute.
- Bridge setup. The iframe is an isolated JavaScript realm and must initialize its own
@infinia/plugin-uiinstance, then bind it to a readyFengYuClient.
Fix.
- CSP: put all JavaScript in external files loaded via
<script src>(the scaffolder writes<script type="module" src="app.js">), and load every asset from the plugin's own/plugin-runtime/{id}/**tree. Do not inline scripts or inline event handlers. - Bridge setup: bundle the plugin's declared Vue/Vuetify dependencies, create Vuetify with
createFengYuVuetify, and callbindFengYuEnvironment(vuetify, fengyu). Check thehost.readyerror for an exact protocol-version mismatch. - For "module not found" errors, install the dependencies declared by the plugin UI and rebuild it with the standard
yarn run buildpath (npm for third-party scaffolds).
See UI Micro-frontend and Pitfalls.
Next steps
- REST API — confirm you are hitting the right endpoint with the right auth.
- SSE Events — stream framing reference.
- Pitfalls — the plugin-author-focused traps, in problem/cause/fix form.