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.
Quick diagnostics
Section titled “Quick diagnostics”# Database containerdocker compose psdocker compose logs db --tail=100
# Public API healthcurl -i http://localhost:7531/api/v1/server/ok
# Server capabilities (configured auth/sync providers + email)curl -s http://localhost:7531/api/v1/server
# Repository checkspnpm checkAPI 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.
Install and tooling
Section titled “Install and tooling”Astro or another dependency rejects Node
Section titled “Astro or another dependency rejects Node”Symptom: Unsupported engine, or the docs fail before compilation.
Use Node 22.12+. Astro 7 is the strictest workspace dependency:
node --versionpnpm --versionThe root manifest pins [email protected] and is the only toolchain source:
corepack enablepnpm --version# 11.8.0Do 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:
pnpm install --frozen-lockfilepnpm checkDo 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.
Production EAS rejects the App Store URL
Section titled “Production EAS rejects the App Store URL”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/id1234567890The 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.
PostgreSQL and migrations
Section titled “PostgreSQL and migrations”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:
docker compose up -d dbdocker compose psFor development on the host, the hostname is localhost. Inside Docker
Compose, the API uses db; the Compose file overrides DATABASE_URL
accordingly.
relation ... does not exist
Section titled “relation ... does not exist”Apply the committed migration journal:
pnpm db:migrateIf you changed packages/db/src/schema.ts, generate and inspect a new migration
instead of editing an old applied migration:
pnpm db:generategit diff -- packages/db/drizzlepnpm db:migrateSee Migration workflow.
API and environment
Section titled “API and environment”API exits with Missing value from ENV
Section titled “API exits with Missing value from ENV”@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:
# correctBETTER_AUTH_URL=http://localhost:7531
# wrongBETTER_AUTH_URL=http://localhost:7531/apiThe client and Better Auth append their own /api/... paths.
A browser request fails CORS
Section titled “A browser request fails CORS”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.
Native client
Section titled “Native client”Expo Go cannot open the project
Section titled “Expo Go cannot open the project”This is expected. Musubi requires native modules. Build a development client:
pnpm --filter @musubi/client android# or, on macOS:pnpm --filter @musubi/client iosA physical phone cannot reach the API
Section titled “A physical phone cannot reach the API”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.
Android emulator cannot reach localhost
Section titled “Android emulator cannot reach localhost”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.
Authentication and email
Section titled “Authentication and email”A social-login button is missing
Section titled “A social-login button is missing”The UI is capability-driven. Inspect:
curl -s http://localhost:7531/api/v1/serversocials 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.
External calendar sync
Section titled “External calendar sync”Provider changes do not appear
Section titled “Provider changes do not appear”- Temporarily set
LOG_LEVEL=debug. - Confirm
EXTERNAL_SYNC_INTERVAL_MINis not0. - Inspect
syncProvidersfrom/api/v1/server. - Trigger a manual refresh in the client.
- Search for
sync.account.*,sync.calendar.completed, andsync.*.failedlog 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.
Documentation site
Section titled “Documentation site”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:
pnpm docs:buildA 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.