Symptom
On a per-environment ObjectOS runtime (cloud distribution, *.objectos.app/*.objectos.ai), after an owner enters via SSO-as-owner, the auth-proxy redirects them (no local credential yet) to /_console/system/profile?recovery_needed=true to "Set a recovery password". Submitting that form POSTs /api/v1/auth/set-initial-password and gets 404 — the recovery flow dead-ends and the user is stuck.
Empirical confirmation (staging crm.objectos.app)
POST /api/v1/auth/set-initial-password → 404 (route not found)
GET /api/v1/auth/get-session → 200 (better-auth IS mounted & responding)
So better-auth itself is fine; the specific route is simply absent on the runtime.
Root cause
set-initial-password is a custom route registered by the full AuthPlugin — packages/plugins/plugin-auth/src/auth-plugin.ts (rawApp.post(\${basePath}/set-initial-password`, …)`, ~L483).
- On a per-environment kernel the
AuthPlugin is skipped ("⚠ AuthPlugin skipped on host kernel — runtime mode (ObjectOSEnvironmentPlugin detected)"). Auth there is served by packages/runtime/src/cloud/auth-proxy-plugin.ts, which special-cases sso-handoff-issue and sso-exchange and otherwise forwards /api/v1/auth/* to better-auth. better-auth has no set-initial-password route → 404.
Net: the recovery flow (auth-proxy redirect → "Set local password") promises an endpoint that only exists on the host-kernel AuthPlugin, not on the runtime. It can never have worked on a per-environment runtime.
Suggested fix
Add a set-initial-password handler to runtime/src/cloud/auth-proxy-plugin.ts (alongside the existing sso-exchange special-case), mirroring the AuthPlugin logic against the environment's auth service (which is already resolvable there — get-session works, so getAuthContext() → internalAdapter + password.hash + createAccount are available):
- require a valid session (else 401),
- reject if a
credential account with a password already exists (409 → "use change-password"),
- enforce min/max length,
internalAdapter.createAccount({ userId, providerId: 'credential', accountId: userId, password: hash }).
This is the same body as auth-plugin.ts L483–536; factoring it into a shared helper both call sites can reuse would avoid drift.
Impact / context
Exposed now that per-environment runtimes boot cleanly (the prior LocalCryptoProvider crash-loop masked it — the runtime never stayed up long enough to reach the recovery page). Blocks the "Open environment as admin" → first-time owner UX end-to-end. framework @ 654a2fd9 (also present at the cloud-pinned cf03ef2f7).
Symptom
On a per-environment ObjectOS runtime (cloud distribution,
*.objectos.app/*.objectos.ai), after an owner enters via SSO-as-owner, the auth-proxy redirects them (no local credential yet) to/_console/system/profile?recovery_needed=trueto "Set a recovery password". Submitting that form POSTs/api/v1/auth/set-initial-passwordand gets404— the recovery flow dead-ends and the user is stuck.Empirical confirmation (staging
crm.objectos.app)So better-auth itself is fine; the specific route is simply absent on the runtime.
Root cause
set-initial-passwordis a custom route registered by the fullAuthPlugin—packages/plugins/plugin-auth/src/auth-plugin.ts(rawApp.post(\${basePath}/set-initial-password`, …)`, ~L483).AuthPluginis skipped ("⚠ AuthPlugin skipped on host kernel — runtime mode (ObjectOSEnvironmentPlugin detected)"). Auth there is served bypackages/runtime/src/cloud/auth-proxy-plugin.ts, which special-casessso-handoff-issueandsso-exchangeand otherwise forwards/api/v1/auth/*to better-auth. better-auth has noset-initial-passwordroute → 404.Net: the recovery flow (auth-proxy redirect → "Set local password") promises an endpoint that only exists on the host-kernel AuthPlugin, not on the runtime. It can never have worked on a per-environment runtime.
Suggested fix
Add a
set-initial-passwordhandler toruntime/src/cloud/auth-proxy-plugin.ts(alongside the existingsso-exchangespecial-case), mirroring the AuthPlugin logic against the environment's auth service (which is already resolvable there —get-sessionworks, sogetAuthContext()→internalAdapter+password.hash+createAccountare available):credentialaccount with a password already exists (409 → "use change-password"),internalAdapter.createAccount({ userId, providerId: 'credential', accountId: userId, password: hash }).This is the same body as
auth-plugin.tsL483–536; factoring it into a shared helper both call sites can reuse would avoid drift.Impact / context
Exposed now that per-environment runtimes boot cleanly (the prior
LocalCryptoProvidercrash-loop masked it — the runtime never stayed up long enough to reach the recovery page). Blocks the "Open environment as admin" → first-time owner UX end-to-end. framework @654a2fd9(also present at the cloud-pinnedcf03ef2f7).