Skip to main content
The startup pill and engine log tell you exactly what the local engine is doing, and every failure attaches the engine’s own output.
Have a shell? praisonai-desktop doctor prints the installed and required praisonaiagents in one line and exits non-zero when they’re out of sync. See Headless CLI.

Quick Start

1

Read the startup pill

The pill shows starting engine, then engine :PORT on success or engine failed with a tail on failure.
2

Open the engine log

The log viewer shows the engine’s recent activity — a bounded 400-line ring buffer — without leaving the app.
3

Reset the engine if it's stuck

Close the app, delete the lockfile in the data directory, and relaunch.

Startup States

On failure, the tail comes from the supervisor’s 12-line buffer, so you see the actual error rather than a bare exit code.

Startup Log (Breadcrumb)

Every launch writes one line to a small log file in the OS temp directory before the window is built, so a launch is always traceable even when no window appears.

Where the file lives

The temp directory is chosen on purpose: it exists before %APPDATA%\PraisonAI is created, so the log is available even on a first launch where the data directory has not yet been created. Creation and provisioning are distinct moments now: the breadcrumb is written in main(), before setup() creates the folder, so the temp log is always the earlier signal.

Line format

Each launch appends one tab-separated line:
A real primary launch writes two lines with the same pid — secondary first (before the process knows it is first), then primary:

Reading the log to diagnose a silent launch

Open the file and read the latest line for the launch you just started. A launch that was handed off to an existing primary writes only its own secondary line and exits with code 0 — that is expected behaviour, not a crash. The data folder is a second signal alongside the log line. It is created as soon as the primary reaches setup(), so a primary line with no data folder points at a permissions or disk problem on the folder’s parent — the app prints [praisonai] could not create <path>: <error> to stderr and keeps going. A missing folder after a secondary-only launch is normal: a handed-off launch creates nothing.
The log is best-effort. If the temp directory cannot be written, the app still starts — a shell must never fail to start because it could not write a log. If the file is missing after a launch, the write itself failed; treat that as a permission or disk issue on the temp directory.

Common Failures

On builds before PraisonAI#4756 the Default model combobox suggested four ids the shipped app cannot run out of the box: claude-sonnet-4-20250514, claude-opus-4-20250514, gemini-2.0-flash, and ollama/llama3.2.
  • Bare Anthropic/Google ids without a Base URL take the OpenAI path and are sent to api.openai.com carrying your OpenAI key, so they fail with an OpenAI error that names platform.openai.com.
  • ollama/llama3.2 is a slashed id, and since PraisonAI#4722 the shipped desktop venv refuses slashed ids (no litellm).
Upgrade to a build that includes #4756 — the picker no longer offers these. To use one of these providers, set a Base URL (Settings → Models → base_url) to the provider’s OpenAI-compatible endpoint and type its bare model id in the combobox. Both fields are Requires restart. See Models & API Keys → Using another provider.
On a first launch where the saved view was Train, the desktop shell restored that view synchronously before the async engine health check finished. When the health check then resolved to setup needed or engine failed, the setup wizard and the failure banner rendered into #thread — which the Train view had already hidden behind body.training #thread { display: none }. The title bar read setup needed, the Train form filled the screen, and there was no visible way to start setup. This was the reported Windows dead end in PraisonAI #4441.Fixed in PraisonAI #4471. showView(name, {persist=true}={}) grew a persist option; a new showEngineGate() calls showView('chat', {persist: false}); boot() runs the gate before both the setup-needed and failed branches. The forced Chat switch is not persisted, so the user’s deliberate Train choice returns once the engine is up. If you saw the wizard vanish behind Train after upgrading or on a clean install, upgrade to this release.A follow-up in PraisonAI #4623 closes the last rough edge: the temporary Chat switch is no longer stuck on the screen until the app is relaunched. boot() now calls a restoreView() helper right after the engine finishes provisioning, which re-reads localStorage.getItem('view') and re-applies it — so the moment you click Get started and setup succeeds, your saved Train view returns in the same session.
The data folder is created as soon as the app opens, so a missing folder after a launch means the shell never got that far. Check the startup log: a primary line with no data folder points at a permissions or disk problem on the folder’s parent (the app prints [praisonai] could not create <path>: <error> to stderr and keeps going, so the window still opens). Only a secondary line means this launch handed off to an already-running app, which does not create anything.
On earlier builds of the Desktop app, the underlying WKWebView shipped with no JS dialog panel, so window.prompt() returned null and window.alert() did nothing. This made three actions look broken:
  • Right-click on a conversation → Move to project issued no request.
  • Adding an MCP server with a blank name silently did nothing.
  • An engine rejection when adding an MCP server showed no error.
If you see any of these on an old build, upgrade to a build that includes PraisonAI #4521. Current builds use in-app dialogs that render regardless of what the WebView’s native dialog panel supports. See the Move to project flow and the MCP Add errors behaviour for what the fixed dialogs look like.
Kind no_answer. The tools succeeded and their results are shown above the banner, but the model produced no follow-up answer for them.From praisonaiagents 1.7.2 onward the library raises loudly instead of ending the stream silently, so this banner is the only user-visible sign. Retry the turn — the tool result is already in the agent’s history, so the retry often answers in plain text.Requires praisonaiagents>=1.7.2 in the engine venv. From PraisonAI #4670 onward, the desktop app compares the shipped requirement against a stamp inside the venv on every launch and re-runs the install once when they differ — so an install stuck on 1.7.1 is brought to the current floor automatically the next time you open the app (the tray reads Engine: updating… while it runs). If you are on a build before that fix and see "the engine produced no output" on every tool call, upgrade the desktop app: pre-4670 builds froze the venv at whatever the first run installed.
A killed dependency install can leave a complete-but-empty venv, so the engine dies with ModuleNotFoundError / ImportError. The startup failure now offers Set up the environment and Copy details buttons and treats an import error as a setup problem rather than an unfixable failure.Click Set up the environment to rebuild the venv. As a fallback, Copy details copies the exact error, and you can reinstall the SDK by hand into the venv the app resolves — the desktop pins a floor of praisonaiagents>=1.7.2:
Closing the app mid-setup — or a killed uv/pip — leaves the venv half-built. On the next launch the startup banner offers two buttons:
  • Set up the environment — rebuild the venv in place (fetch uv, install a pinned CPython, reinstall praisonaiagents).
  • Copy details — copy the exact error to paste into an issue.
Click Set up the environment first; use Copy details only if the rebuild also fails.
On earlier releases the desktop app built its Python venv once, during first-run setup, and never revisited it. Setup ran only from the Get started button; a normal launch started the engine from an existing venv without checking what was inside. So a fix shipped in praisonaiagents after that reached brand-new installs only — raising the floor in one release changed nothing for installs that already existed. A user stuck on praisonaiagents 1.7.1 kept hitting "the engine produced no output" on every tool call while the fix had been on PyPI for days, with no in-app way to correct it. The workaround was deleting the venv by hand.Fixed in PraisonAI #4670 (closes #4668). The app now writes a .praisonai-packages stamp inside the venv and, on every launch, compares it against the current requirement — if they differ, it re-runs the install step once, rewrites the stamp on success, and starts the engine against the up-to-date venv. The tray reads Engine: updating… while it runs; a no-drift launch is not slowed because uv is near-instant when nothing changed. Failure never blocks chat: the engine still starts and the UI reports whatever version is actually installed, and because the stamp is only written on install success, a failed update is retried next launch rather than silently recorded as done. Reconciliation only touches the app’s own venv (<data>/venv) — a PRAISONAI_PYTHON override or a checkout venv is left untouched, its owner manages its packages.
A shell that exports PYTHONHOME, PYTHONPATH, or PYTHONSTARTUP — common with conda, distro tooling, or a systemd user session — used to have those variables passed straight through to the engine. That redirected the engine’s stdlib or site-packages away from the venv the shell had just proved. The engine either failed to start with ModuleNotFoundError for something clearly present in .venv/, or worse, started against another environment’s praisonaiagents and behaved inconsistently.Fixed in PraisonAI #4519 (closes #4500). The shell now strips PYTHONHOME, PYTHONPATH, and PYTHONSTARTUP; sets VIRTUAL_ENV to the venv root; and prepends the venv’s bin/Scripts to PATH. Every other variable passes through unchanged.If you saw the engine importing from the wrong environment, upgrade to this release. To point the engine at a local praisonaiagents checkout, use PRAISONAI_AGENTS_SOURCE — see the Environment Variables page.
Clicking a Settings row when the engine rejected the write left the UI in one of three misleading states:
  • Theme did nothing — no data-theme, no highlight, no error, no toast. The button read as broken.
  • Toggles (confirm_delete, auto_title, show_reasoning, …) moved in memory but not on screen, so the switch and the persisted value disagreed and the next click flipped the wrong one back.
  • Text size toasted Text size 16 px while every write silently failed, and two rapid ⌘+ presses collapsed to a single step.
Fixed in PraisonAI #4520 (closes #4497). saveCfg now persists first, checks r.ok, surfaces Could not save that setting. on failure, and returns a boolean. apply gates applyTheme/modelName.textContent/renderSettings on that boolean. setTextSize awaits and only toasts the size that actually stuck. Writes are serialised through a saveQueue promise chain so a burst of clicks resolves in order.Any Settings write against an unreachable or restarting engine now shows a Could not save that setting. toast and leaves the row exactly where it was. If you saw a dead Theme button after changing base_url or api_key, upgrade to this release; the button was never dead, the engine was mid-restart.
GET /settings had always masked the api_key as bullets, but POST /settings replied with save_settings()’s merged dict — which starts from load_settings(), and that reads the real secret back out of the OS keychain. So every settings write answered with the credential in cleartext, including a write that only changed theme and never mentioned the key. Combined with Access-Control-Allow-Origin: *, any page open in a browser could read it.Fixed in PraisonAI #4680. A single redacted() helper is now applied at both GET /settings and POST /settings, so every settings response masks each SECRET_KEYS entry as bullets. An empty key still comes back as "" so the UI can tell “set” from “unset”. What is stored is unchanged; only what leaves the process is masked. If you inspected a settings write on an earlier release and saw your key in cleartext, upgrade — and rotate that key.
Opening Settings and clicking Export all while the engine was down gave no error and no toast. The clipboard still held whatever it had from earlier in the session, so pasting it into a .json file could look like a valid backup and be treated as one.Fixed in PraisonAI #4518 (closes #4502). runAction now try/catch-es its work and surfaces failures via toast('That did not run: …'). Export also checks if(!r.ok) throw before touching the clipboard, so a failed export can no longer overwrite the clipboard with an incomplete payload.Any Settings action against an unreachable engine now shows a toast prefixed That did not run:. If you relied on Export as a backup path on an earlier release, re-verify any Export you took while the pill read engine failed or starting engine.
On a CP932 (Japanese) or CP1252 (Western European) system the engine’s reader used to die on the first undecodable byte, the port announcement was lost, and startup blamed the 30-second timeout instead of the real cause.The shell now exports PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 to the engine and reads its output loss-tolerantly, so this is fixed. Setting PYTHONUTF8=1 yourself is redundant — the shell already does it.
On a Wayland host with no appindicator, tray initialisation can fail — the app still launches. Tray failure is logged rather than fatal, so a missing tray icon is not a crash. On Debian/Ubuntu, install libayatana-appindicator3-1 (or libappindicator3-1) to get the icon back.
On Windows a training Stop used to report success while the run kept going and held the GPU — the stop signal needed a console the trainer did not have. This is fixed: Stop now uses taskkill /T /F. If you observed it, upgrade to this release.
On Windows the shell compared the interpreter path in the lockfile (sys.executable, always backslashed) against the expected path (built with a forward slash), and a byte mismatch escalated to taskkill /T /F. That took the engine and its training subprocess with it on every relaunch, so a fine-tune could never survive closing the window.Fixed in PraisonAI #4515 (closes #4499). The shell now folds separators and case on Windows before comparing. If you saw a fine-tune disappear every time you reopened the app, upgrade to this release.
The engine kept the live run and the history in memory only, so a crash or kill left /train/runs returning [] while the trainer kept the GPU — and start was willing to launch a second trainer beside the live one. Fixed in PraisonAI #4510: the run’s state and pid are persisted to runs/<run-id>/run.json at every transition, and the next engine boot rebuilds history from disk. A run is adopted only when its recorded pid and its start-time fingerprint still match the live process; an interrupted run whose pid is gone reads as failed, not missing. If you saw a run disappear across a restart, upgrade.Adopting purely on a live pid was itself a hazard — pids are recycled, and adopting a recycled one left the run pinned running forever (start refused every new fine-tune until someone deleted run.json by hand) while Stop SIGTERM’d whatever unrelated program now held that pid. PraisonAI #4680 added the start-time fingerprint (pid_start) so adoption proves ownership before reattaching; a pre-upgrade state file with no fingerprint is refused rather than adopted, at the cost of at most one run shown as interrupted across a single restart after upgrade. If fine-tuning was wedged with no live process, this is fixed — you no longer need to delete run.json by hand.
A single OSError during log writing abandoned the pipe while the trainer was still writing. The child blocked on a full pipe, proc.wait() blocked on the child, run.finish() never ran, and the run’s state stayed running forever — so POST /train/start returned 409 for every subsequent run until the engine was restarted.Fixed in PraisonAI #4509 (closes #4493). The reader now always drains the pipe, log errors are caught per-line, and every line is flush()ed so train.log is the live full record. If you saw a run pinned to running and every subsequent Start refused, upgrade.
On Quit, the engine exited but the trainer — spawned in its own session — was reparented to init, kept the GPU, and became invisible to the app.Fixed in PraisonAI #4508 (closes #4491). The engine’s exit handler now calls Trainer.stop() before exiting, sending SIGTERM to the trainer’s own session/group. Windows was already correct (taskkill /T) and is unaffected. If you saw the GPU stay pinned after quitting on macOS or Linux, upgrade.
Set PRAISONAI_AGENTS_SOURCE to your checkout before launching:
Without the override, the engine walks up from its own file looking for praisonai-agents/ or src/praisonai-agents/. Earlier builds used a fixed path that resolved to a nonexistent directory from the bundled copy, so any local fix appeared to have no effect — that is now fixed.
Symptom (before this fix): the /chats, /projects, and /search endpoints dropped the connection with no status, so the front end blamed the engine for being down. Reproduced by placing a JSON array (for example, the app’s own Export clipboard payload) into chats/.Fix: upgrade to the release that includes PraisonAI #4512. The engine now treats any non-object JSON file in chats/ as (unreadable) and keeps serving the other conversations.What to do with the file: the wrong-shape file appears as an (unreadable) row. To load those conversations properly, split the Export array into one JSON object per chat and drop each object into chats/ as its own file (<id>.json).
Another PraisonAI process holds the port (Address already in use). Quit the other process, or reset the engine and relaunch.
An unhandled exception reached the top of the stack. The tail is shown — open the Engine Log for the full 400-line buffer.
The app checks src/praisonai-agents/.venv, src/praisonai-agents/venv, then venv. Create one of these and install praisonaiagents into it.
The single-instance guard exits a secondary launch with code 0 after raising the existing window into the tray, so a bare Start-Process -PassThru reads that as a crash. Open %TEMP%\PraisonAI-startup.log: a secondary line for that pid confirms the launch was handed off cleanly and the primary is still running — check the system tray. If no line was written at all, the shell died before it could even trace itself; attach the missing log path to a bug report.
Turning on Open at login requires a macOS .app bundle so the engine can register a LaunchAgent. When the app runs from a checkout, or on Windows or Linux, the OS refuses the request. As of PraisonAI #4507 the engine persists the honest result (false) instead of the request (true), the toggle snaps back to off, and the inline row message shows Only available in the installed app. If you’re on macOS and want to test it from a checkout, set PRAISONAI_APP_BUNDLE to your built .app path before launching. On an earlier release, upgrading is the fix — pre-4507 the toggle would render on and survive restarts while no login item existed anywhere.
The fallback “No output” banner now suppresses itself when the engine has already reported a specific error kind (no_answer, auth, rate_limit, internal) and after Stop — one turn produces at most one banner.

Updates

The update check asks PyPI what the newest praisonaiagents is: GET /update hits https://pypi.org/pypi/praisonaiagents/json and reports whether a newer release exists. The check_updates toggle controls whether the app checks on startup and from About → Check for updates now. A check that did not happen reports checked: false rather than falsely reading as “up to date”.
The PyPI update check is a version check only — the app does not automatically upgrade to a newer PyPI release. Install a newer praisonaiagents by re-running provisioning, or by upgrading your own pinned venv if you set PRAISONAI_PYTHON.The app does automatically reconcile its own venv to the requirement shipped with the current build (see Library fixes never reached an existing install (pre-4670)). So a raised floor in the shipped requirement reaches you on the next launch; a newer PyPI release beyond that floor still requires a manual step.

Reset Recipe

1

Quit the app

Fully close the window so the engine process exits.
2

Delete the lockfile

Remove the lockfile in the data directory for your platform:
3

Relaunch

Reopen the app — the shell spawns a fresh engine and the pill returns to starting engine.

Best Practices

The engine log is a 400-line ring buffer of recent activity. It usually names the failure directly.
“Address in use” comes from a second process on the port. Close extras before relaunching.
The shell refuses an interpreter whose site-packages live outside its own venv. Use a clean .venv inside the checkout to avoid mismatches.

Overview

Install, launch, and how the engine starts

Data & Privacy

Where the data directory and lockfile live