Skip to content

Absorb single-user mode into multi-user (phases 1-3)#380

Open
chadbyte wants to merge 4 commits into
mainfrom
feat/absorb-single-user
Open

Absorb single-user mode into multi-user (phases 1-3)#380
chadbyte wants to merge 4 commits into
mainfrom
feat/absorb-single-user

Conversation

@chadbyte

@chadbyte chadbyte commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Removes the separate single-user runtime: every deploy now runs in the (general) multi-user model. A solo user is just a one-user multi-user deploy with an auto-provisioned default admin and no login wall. OS-user isolation is orthogonal and untouched.

Principle: migrate-then-flip — safety nets and data migration land before the default flips, so existing deploys upgrade with zero manual steps.

Phases in this PR

  • Phase 1 — solo auto-login safety net. getMultiUserFromReq() falls back to users.getSoloNoPinUser() (exactly one user, no PIN), so every HTTP/WS auth gate is satisfied with no login wall. Inert until multi-user.
  • Phase 2 — auto-migrate legacy single-user deploys (lib/migrate-single-user.js, at daemon boot): carry the PIN hash (same PIN still logs in), profile.json, settings (chatLayout, autoContinueOnRateLimit, matesEnabled, terminalFont, deletedBuiltinKeys, mateOnboardingShown), move flat mates → mates/<userId>/, backfill ownerId on legacy sessions, flip multiUser=true. Backups + idempotent (config marker) + best-effort per step; no-op on fresh / already-multi.
  • Phase 3 — default new installs to multi-user. Drop the "Just me / Multiple users" prompt; boot calls ensureMultiUser() (migrate legacy, or provision a default no-PIN admin on fresh installs); all mode fallbacks flipped "single""multi".

Net: isMultiUser() is effectively always true and there's always ≥1 user; single-user code paths are now dead (removed in follow-up phases 4-6).

Verification (isolated CLAY_HOME harness — real ~/.clay untouched)

  • Legacy single-user with PIN → migrates; original PIN authenticates, wrong PIN rejected; profile/settings/session-ownerId/mates carried; backups + marker; idempotent on re-run.
  • Legacy single-user no PIN → migrates; solo auto-login resolves.
  • Fresh install → provisions default no-PIN admin; idempotent.

Static checks: node --check on all touched files.

Follow-up (handoff)

docs/ongoing/SINGLE-USER-ABSORPTION.md documents the remaining cleanup — phase 4 (settings dc→user-record dual-path, with an avatar-logic caveat), phase 5 (!isMultiUser branch removal across ~27 files), phase 6 (flag/dead-code removal) — plus a watch-out: transitioning a solo (no-PIN) deploy to real multi-user must force the admin to set a PIN first (not yet handled).

Note: static-validated + isolated-harness-tested. A live daemon E2E (boot against a throwaway CLAY_HOME/port, confirm PIN login / no-wall solo / sessions+settings intact) is recommended before rollout.

🤖 Generated with Claude Code

chadbyte added 4 commits July 1, 2026 20:02
…e 1)

Groundwork for folding single-user mode into multi-user. When a multi-user
instance has exactly one user with no PIN, authenticate them automatically
instead of showing a login wall, preserving the frictionless single-user
onramp.

Central change: getMultiUserFromReq() falls back to users.getSoloNoPinUser()
when there's no valid token, so every HTTP and WS auth gate (all route
through this) is covered by one path. Disabled the moment a PIN is set or a
second user is added.

Inert today: only triggers when multiUser is true (single-user deploys are
unaffected until the later migration phase flips them).
…ase 2)

On daemon boot, detect a legacy single-user deploy and fold it into the
multi-user model with zero manual steps:
- provision one "admin" user that inherits the single-user PIN hash (same
  PIN still logs in), profile.json, and settings (chatLayout,
  autoContinue, matesEnabled, terminalFont, deletedBuiltinKeys,
  mateOnboardingShown)
- move legacy flat mates into the per-user mates dir
- backfill ownerId onto legacy sessions so they belong to the user
- flip multiUser = true

Runs before projects/mates/sessions load. Heavily guarded: backs up
users.json/daemon.json/profile.json first, is idempotent (config marker),
tolerates partial failure per step, and is a no-op on fresh installs and
already-multi-user deploys. Combined with the phase-1 solo auto-login, a
no-PIN single-user deploy upgrades with no login wall.
…(phase 3)

There is no separate single-user runtime anymore. Every deploy runs in the
(general) multi-user model:

- Remove the "Just me (single user) / Multiple users" setup question. Fresh
  installs go straight to multi-user; OS-user isolation stays an opt-in
  (offered on Linux at setup, toggleable later).
- daemon boot calls ensureMultiUser(): migrate a legacy single-user deploy,
  or provision a default no-PIN "admin" on a fresh one. With phase-1 solo
  auto-login, a solo user gets in with no account creation and no login wall.
- Flip every mode fallback from "single" to "multi".

Solo onramp is unchanged in feel (npx clay-server -> just works); it's now a
one-user multi-user deploy under the hood.
Capture the plan, what's done (phases 1-3 + verification), and the remaining
cleanup (phase 4 settings dual-path, phase 5 !isMultiUser branch removal,
phase 6 flag/dead-code removal) with file:line targets and watch-outs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant