Skip to content

Troubleshooting

Start with the smallest failing boundary. Confirm PostgreSQL, then the API health route, then authentication, then the client. For provider problems, first prove normal Musubi events work so the failure is isolated to the adapter.

Terminal window
# Database container
docker compose ps
docker compose logs db --tail=100
# Public API health
curl -i http://localhost:7531/api/v1/server/ok
# Server capabilities (configured auth/sync providers + email)
curl -s http://localhost:7531/api/v1/server
# Repository checks
pnpm check

API logs are JSON. Copy the x-request-id response header from a failed request and search logs for the same requestId; request middleware, auth, handlers, sync, and the error middleware share that correlation value.

Symptom: Unsupported engine, or the docs fail before compilation.

Use Node 22.12+. Astro 7 is the strictest workspace dependency:

Terminal window
node --version
pnpm --version

The root manifest pins [email protected] and is the only toolchain source:

Terminal window
corepack enable
pnpm --version
# 11.8.0

Do not install a separate global pnpm or add package-local packageManager fields.

pnpm reports a stale or incompatible install

Section titled “pnpm reports a stale or incompatible install”

Run from the repository root:

Terminal window
pnpm install --frozen-lockfile
pnpm check

Do not switch the workspace back to pnpm’s isolated linker: Expo and Metro rely on the configured nodeLinker: hoisted. If a dependency change intentionally updates the lockfile, run pnpm install, inspect pnpm-lock.yaml, and commit the manifest and lockfile together.

Symptom: config evaluation reports that EXPO_PUBLIC_IOS_APP_STORE_URL is required or malformed.

Set a direct public listing URL in the production EAS environment:

EXPO_PUBLIC_IOS_APP_STORE_URL=https://apps.apple.com/app/musubi/id1234567890

The numeric id must be the real App Store Connect app id. Development and preview builds can omit the variable and use the public download page fallback; production intentionally cannot.

ECONNREFUSED or the API exits during import

Section titled “ECONNREFUSED or the API exits during import”

Check that DATABASE_URL points to a reachable server:

Terminal window
docker compose up -d db
docker compose ps

For development on the host, the hostname is localhost. Inside Docker Compose, the API uses db; the Compose file overrides DATABASE_URL accordingly.

Apply the committed migration journal:

Terminal window
pnpm db:migrate

If you changed packages/db/src/schema.ts, generate and inspect a new migration instead of editing an old applied migration:

Terminal window
pnpm db:generate
git diff -- packages/db/drizzle
pnpm db:migrate

See Migration workflow.

@musubi/config loads only the repository-root .env. Confirm the file exists next to the root package.json, not only inside apps/api/.

The direct boot requirements are DATABASE_URL, ENVIRONMENT, and BETTER_AUTH_URL; Better Auth also needs a stable BETTER_AUTH_SECRET. Use the environment reference for feature-specific keys.

Requests go to /api/api/... and return 404

Section titled “Requests go to /api/api/... and return 404”

BETTER_AUTH_URL must be the origin only:

# correct
BETTER_AUTH_URL=http://localhost:7531
# wrong
BETTER_AUTH_URL=http://localhost:7531/api

The client and Better Auth append their own /api/... paths.

Development currently allows the configured API origin plus http://localhost:3000 and http://localhost:8081. Native requests normally have no browser origin. If you add a web client on a different port, update allowedOrigins in apps/api/src/index.ts; do not use a wildcard with credentials.

This is expected. Musubi requires native modules. Build a development client:

Terminal window
pnpm --filter @musubi/client android
# or, on macOS:
pnpm --filter @musubi/client ios

localhost on the phone is the phone itself. Use your computer’s LAN address in both the welcome screen and BETTER_AUTH_URL, then restart the API. Keep the phone and computer on the same network and allow port 7531 through the local firewall.

Use pnpm --filter @musubi/client start for LAN Metro discovery. The workspace’s dev script deliberately starts Metro with --localhost, which is for simulators.

Use http://10.0.2.2:7531. Android maps 10.0.2.2 to the host loopback.

The first frame has the wrong theme or no cached events

Section titled “The first frame has the wrong theme or no cached events”

Settings hydrate synchronously from SQLite, while calendars/events hydrate asynchronously in the tabs layout. Check the native SQLite migration first (apps/client/drizzle/) and then services/eventsCache.ts. Do not convert the settings seed to an async effect; that reintroduces a first-frame theme flash.

The UI is capability-driven. Inspect:

Terminal window
curl -s http://localhost:7531/api/v1/server

socials controls sign-in buttons; syncProviders controls calendar connection options. Both the server credential and, for native Google sign-in, the build-time EXPO_PUBLIC_* values must be present. See Authentication setup.

Password reset or account deletion cannot send

Section titled “Password reset or account deletion cannot send”

The API can boot without SMTP, but those flows need all SMTP_* values. The server advertises email: false when SMTP_HOST is empty and the client hides or adapts the UI.

  1. Temporarily set LOG_LEVEL=debug.
  2. Confirm EXTERNAL_SYNC_INTERVAL_MIN is not 0.
  3. Inspect syncProviders from /api/v1/server.
  4. Trigger a manual refresh in the client.
  5. Search for sync.account.*, sync.calendar.completed, and sync.*.failed log events.

Provider failures also increment musubi_external_sync_failures_total{stage,provider}. See Observability.

Google or Microsoft says reconnect is required

Section titled “Google or Microsoft says reconnect is required”

An invalid_grant marks only that OAuth account as reconnect_required and stops background retries. Relink the same account; the Better Auth account hook returns it to active after a fresh refresh token with calendar scope arrives.

iCloud accepts credentials but imports no events

Section titled “iCloud accepts credentials but imports no events”

Use an Apple app-specific password, not the Apple ID password. Musubi’s CalDAV adapter sends the time range iCloud requires; an initial-sync error is returned to the connect screen instead of reporting false success. Enable debug logs and follow the CalDAV guide.

Tables disappear or Markdown warnings appear

Section titled “Tables disappear or Markdown warnings appear”

Astro 7 uses Sätteri for Markdown and MDX, with GFM and smart punctuation enabled by default. Do not re-add the deprecated top-level markdown.gfm or markdown.smartypants options. Run:

Terminal window
pnpm docs:build

A new page builds but is not in the sidebar

Section titled “A new page builds but is not in the sidebar”

This site uses an explicit sidebar in packages/docs/astro.config.mjs so the learning sequence stays intentional. Add the page there and include it in the nearest landing page. The documentation guide has the author checklist.