diff --git a/packages/cli/src/commands/doctor.ts b/packages/cli/src/commands/doctor.ts
index d949a9d5f..4d40bc348 100644
--- a/packages/cli/src/commands/doctor.ts
+++ b/packages/cli/src/commands/doctor.ts
@@ -5,7 +5,11 @@ import chalk from "chalk";
import postgres from "postgres";
import { checkMemoryHealth } from "./memory/_lib/openclaw-cmd.js";
import { resolveServerUrl } from "./memory/_lib/openclaw-auth.js";
-import { isPortFree } from "./dev.js";
+import {
+ isExternalDatabaseUrl,
+ isPortFree,
+ resolveEmbeddedDataRoot,
+} from "./dev.js";
import { parseEnvContent } from "../internal/env-file.js";
import { loadProviderRegistry } from "./providers/registry.js";
import { loadProjectConfig } from "./_lib/apply/desired-state.js";
@@ -203,8 +207,21 @@ export async function doctorCommand(
checks.push(checkBinaryExists("git"));
const databaseUrl = env.DATABASE_URL ?? process.env.DATABASE_URL;
- if (databaseUrl) {
+ if (databaseUrl && isExternalDatabaseUrl(databaseUrl)) {
checks.push(...(await checkDatabaseAndPgvector(databaseUrl)));
+ } else if (databaseUrl) {
+ // Embedded Postgres: DATABASE_URL is a filesystem path (often `file://
`,
+ // the scaffold default `file://.`), not a connection string. `lobu run`
+ // boots a self-contained PG18 + bundled pgvector under `/.lobu/pgdata`,
+ // so there is nothing to dial until it's running. Report the resolved data
+ // root instead of feeding the path to postgres() — `postgres("file://.")`
+ // parses host "." and fails with `getaddrinfo ENOTFOUND .`.
+ const root = resolveEmbeddedDataRoot(databaseUrl);
+ checks.push({
+ name: "database",
+ status: "ok",
+ detail: `local embedded Postgres (data: ${join(root, ".lobu", "pgdata")})`,
+ });
} else {
checks.push({
name: "database",
diff --git a/packages/server/src/dev-vite.ts b/packages/server/src/dev-vite.ts
index 2156d3e8a..bea38bd25 100644
--- a/packages/server/src/dev-vite.ts
+++ b/packages/server/src/dev-vite.ts
@@ -54,6 +54,19 @@ export async function mountViteDev(
honoListener: HttpListener
): Promise<{ close: () => Promise } | null> {
if (process.env.NODE_ENV !== 'development') return null;
+ // A bundled CLI (`lobu run` from an npm install) ships the prebuilt SPA and
+ // points WEB_DIST_DIR at it; the Hono app serves that statically. There is no
+ // SPA *source* to run Vite against — and no HMR wanted for a prebuilt run — so
+ // skip Vite entirely. Otherwise we'd probe for `packages/owletto`, fail, and
+ // log a misleading "frontend will not be available" error even though the
+ // frontend is in fact served from the bundle.
+ if (process.env.WEB_DIST_DIR?.trim()) {
+ logger.info(
+ { webDistDir: process.env.WEB_DIST_DIR.trim() },
+ 'Serving prebuilt SPA from WEB_DIST_DIR — skipping Vite dev server'
+ );
+ return null;
+ }
try {
const { createServer } = await import('vite');
const vite = await createServer({
diff --git a/scripts/sdk-e2e.sh b/scripts/sdk-e2e.sh
index 370cab9b6..5acc0485f 100755
--- a/scripts/sdk-e2e.sh
+++ b/scripts/sdk-e2e.sh
@@ -205,6 +205,28 @@ TS
export LOBU_PROVIDER_REGISTRY_PATH="$HARNESS/providers.json"
+# 2c) Static CLI checks (no server needed): the typed-config validator and the
+# doctor health check. doctor must NOT false-fail the DB check on the scaffold's
+# embedded `DATABASE_URL=file://.` — it once fed that path straight to
+# postgres(), which parses host "." and dies with `getaddrinfo ENOTFOUND .`.
+# We assert the embedded backend is recognized and that no connect error is
+# printed. doctor's own exit code is ignored: the gateway isn't up yet, so its
+# "server unreachable" check is expected to trip independently of the DB line.
+VALIDATE_OUT="$RUN_DIR/validate.out"
+( cd "$PROJ" && $LOBU validate > "$VALIDATE_OUT" 2>&1 ) || { cat "$VALIDATE_OUT" >&2; fail "lobu validate failed on the fixture config"; }
+grep -qi "is valid" "$VALIDATE_OUT" || { cat "$VALIDATE_OUT" >&2; fail "lobu validate did not report the config valid"; }
+echo "✓ lobu validate accepts the fixture config"
+
+DOCTOR_OUT="$RUN_DIR/doctor.out"
+( cd "$PROJ" && $LOBU doctor > "$DOCTOR_OUT" 2>&1 ) || true # non-zero ok (gateway not up yet)
+if grep -qiE "connect failed|ENOTFOUND" "$DOCTOR_OUT"; then cat "$DOCTOR_OUT" >&2; fail "lobu doctor false-failed the DB check (embedded file:// fed to postgres())"; fi
+# The embedded-recognition message only applies when running against embedded PG
+# (the default). With an external DATABASE_URL, doctor connects for real instead.
+if [ -z "${DATABASE_URL:-}" ]; then
+ grep -qi "embedded Postgres" "$DOCTOR_OUT" || { cat "$DOCTOR_OUT" >&2; fail "lobu doctor did not recognize the embedded Postgres backend"; }
+fi
+echo "✓ lobu doctor reports a healthy DB (no false connect failure on embedded file://)"
+
# 3) Boot lobu run — it auto-applies the project (the apply + prune E2E).
( cd "$PROJ" && $LOBU run --port "$GW_PORT" > "$RUN_LOG" 2>&1 ) &
for _ in $(seq 1 80); do