--- name: agenthub-multi-user-goal description: Multi-user AgentHub server — built as a server MODE inside AgentHub (no fork); plan in docs/plans/server-mode/ (AgentHub-Server repo deleted 2026-10-09) metadata: type: project --- Goal (user, 2026-10-02): AgentHub as a shared web server — login, one folder per account on the shared disk, each user sees only their own stats/tokens/prompts; admin sees and manages all; data analysts analyse token use and prompts; a shared AI Router (key exposure is not a concern); every user can switch the shared Claude native / Codex accounts and use every installed tool without reinstalling or logging in again. Company is on Claude Enterprise, one seat per person. The plan was first kept in a separate AgentHub-Server repo. The user deleted that repo on 2026-10-09, and its README now lives in AgentHub as docs/plans/server-mode/README.md (branch s3-followups). There is one source now: AgentHub. **Why:** the local app is still moving fast; a fork now would mean merging every change twice. **How to apply:** build the server features as a server MODE inside the AgentHub codebase, off by default. Deployment pieces live in AgentHub too (scripts/server/, docs/SERVER-MODE.md). Fork only from a tagged stable release. User said "từ từ" — do the local work first. **Progress:** 2026-10-06 the user confirmed one codebase + an install-time choice (`agenthub server setup`) over a separate add-on package, and S1 was built UNCOMMITTED: `server/lib/auth.js`, `/api/auth`, `users`/`auth_sessions`, `AuthGate.tsx`, `agenthub server|users`, docs/SERVER-MODE.md. 2026-10-07 the user chose: admin signs every Claude account in, each new session auto-balanced on usage/quota/$ — built the Claude account pool (server/lib/claude-pool.js, setup-tokens via CLAUDE_CODE_OAUTH_TOKEN, `agenthub accounts`), also uncommitted; user said "commits sau". 2026-10-07 S2 ownership + S6 basics + review fixes built (server/lib/scope.js, audit.js, routes/users.js, pane tokens); checklist docs/plans/server-mode/CHECKLIST.md; still uncommitted. Left: S0 spike, S3 (Linux user per account), S4 — all need sudo on a host. **2026-10-08 scope decided** (user's 7 asks): own workspaces/sessions/stats (S2 done) + own Linux user (S3) + own skills/plugins = admin baseline + user adds own + own AI Proxy = per-user store PLUS admin's shared proxies visible to all (no key leak) + shared tool binaries (/opt, S4) + shared Claude native pool (done) + NEW Codex multi-account pool (like claude-pool). User allowed the S0 spike with sudo ON THIS MACHINE (test users agh-test1/agh-test2; log every command for undo). Checklist: docs/plans/server-mode/CHECKLIST.md. **2026-10-08 plan review decisions** (full plan: docs/plans/server-mode/S3-PLAN.md): hidepid=invisible mandatory; HIDE shared proxy keys via an AgentHub loopback gateway (pool OAuth tokens excluded); Codex ChatGPT accounts used CONCURRENTLY (user overrode the safe 1-pane default → P7 starts with a real-account spike; report back if codex self-refresh logs others out); members get NO managed-worktree mode and NO runtime layer (EMEMBERLANE). Read access = system unit + CAP_DAC_READ_SEARCH, but NOT NoNewPrivileges/CapabilityBoundingSet (they break sudo -u). Hooks auth = peer uid via /proc/net/tcp (verified). Code waits on branch `server-s3`; commits only on ask. **2026-10-08 progress:** worktree `.claude/worktrees/server-s3` (branch server-s3, node_modules symlinked to main's, UNCOMMITTED). P0 (ai-tools config staff-only, member cc-config cwd scoped), P1 (personal proxies `u_`), P1b (proxy key gateway /api/proxy-gw, server mode only; real claude+codex verified) DONE. Next P2 needs sudo + a decision on how far to touch this dev box (prod dashboard runs here as user unit on 4820). Single-suite runner: /tmp/agh-spike/t1.sh (REPORTER=spec). **2026-10-08 P2 INSTALLED on this machine** (user chose full install): prod dashboard :4820 now = SYSTEM unit agenthub.service running the server-s3 WORKTREE code (env file ~/.claude/agenthub/runtime/agenthub.system.env); user unit disabled; hidepid=invisible on /proc; /etc/sudoers.d/agenthub; /etc/claude-code/managed-settings.json (member-hook); ah-admin + ah-vinnt exist. Undo: /tmp/agh-spike/UNDO.sh (step 0 = `install-system --uninstall`). A plugin update while this runs may race (old bootstrap doesn't know the system unit). **2026-10-08 P3–P5 + P8 review fixes done & redeployed on this box** (still UNCOMMITTED on server-s3): 4-lens review found the host acting on member-owned paths (tmux client on member socket, writes into handoff, cap reads via swapped links) → fixed: all member tmux via sudo -u (+ pane-launch launch file), all home writes via write-as, fd+uid-checked reads, setpriv drops ambient cap for children. Live e2e as s3probe passed (pane, WS attach, Claude via gateway "pong", stage-hunk, partial commit, drop, symlink-swap refused). P7 spike done (free ChatGPT acct now in host ~/.codex): an OLD refresh token still refreshes after rotation → concurrent panes OK. Left: P6 (seed ~/.claude.json onboarding/trust, managed plugin baseline), P7 build, user's :4830 dev server still abandons member runs. **2026-10-08 role rename hazard:** another session's UNCOMMITTED main-checkout work renamed role `member`→`developer` and migrated the SHARED dashboard.db (users CHECK now admin/analyst/developer). server-s3 bridges it in auth.js (`roleIn`/`roleOut`, ponytail) — before that, a developer on :4820 saw everything. At merge: take main's rename, drop the bridge. Also done 10-08: per-account first-run wizard + spotlight tour (`/api/me/prefs`, docs/plans/onboarding), P6 provision seed. **2026-10-09 S3 code-complete and on origin/main (84e2a9f0, not released):** P4 per-member quota, P6 plugin baseline (managed-settings spike script written, NOT run), P7 Codex pool, and five review rounds F1–F49, each fix with a regression test. All of it was verified with fake sudo only. Still open: - a live test on real accounts/sudo; - the P6 spike; - async tmux/copy (two ponytail ceilings); - an Express-5 upgrade must drop the Layer.handle_request patch in server/index.js. Work branch s3-next (worktree kept). Round-3 findings: ~/.cache/agenthub-s3/r3/. **2026-10-09 follow-ups (branch s3-followups → released in v2.6.0):** - plan README moved in; - Express patch guarded to 4.x; - P6 spike RUN on agh-test1: managed settings work from the first interactive session (onboarding must be seeded); `claude plugin list` stays [] because no user-scope record is written; details in S3-PLAN P6. Log: ~/.cache/agenthub-s3/LIVE-LOG.md; - all of it RELEASED in v2.6.0 (fa156d6a) on 10-09; async member tmux listing and async snapshot copy included; server suite 2840/2843 (3 = port 3000 env). Remaining ceiling: a click/hasSession on a frozen member still blocks ≤3 s once per back-off. **2026-10-12 prod box update:** the system unit still runs `.claude/worktrees/server-s3`, which had been left on a WIP commit over v2.5.12, so the dashboard's Update failed (`merge --ff-only` on a diverged branch; that old code predates the v2.6.1 `diverged_from_release` guard). Fixed by moving branch server-s3 to v2.7.1 (`reset --keep`; WIP pinned as branch `server-s3-wip-87903d47`), then `NODE_ENV=development npm run setup`, `NODE_ENV=production npm run build`, `sudo -n systemctl restart agenthub.service` (sudo -n works here). Future updates of this box should fast-forward cleanly. **2026-10-12 live e2e as `test` (developer, pw 123456789, provisioned ah-test by the user):** scoping, WS, AI proxy, git as member, shell pane, isolation all OK. Root cause of "user's :4830 dev server abandons member runs" found and fixed in v2.7.2 (`linux-accounts.unseenMemberFolder`) — the OLD :4830 process still breaks member panes until restarted. Left to test after prod updates to 2.7.2: Claude via 9router gateway, Codex, member quota, skills in session. Open UX findings: ensure/start accept a missing folder (pane falls back to home), no mkdir for a new member, `--allow-dangerously-skip-permissions` always passed → Trust + Bypass consent prompts on first run. Auto-mode classifier blocks: sudoers log tweaks (audit tampering), `users provision` (permission grant). Leftovers: lane 49, proxy u5_e2e-probe, ~/proj in ah-test home, ~/.cache/agenthub-e2e. **2026-10-12 v2.7.3 on prod (:4820 system unit, worktree server-s3 = 5d8fc9bb):** full member e2e as `test` PASSED — Claude + Codex via 9router gateway (pong), runs stay running, tokens recorded, home prompt box -> reply, Chat UI -> reply, no API >=400 for a developer, plugin baseline sg-all in member cache. Root causes fixed: (1) self-restart under the system unit crash-looped it (detached replacement held the port; recover = SIGTERM the orphan); (2) :4830 dev server runs in SERVER MODE (settings.env AGENTHUB_MODE=server) without CAP_DAC_READ_SEARCH -> EACCES on member 0700 tmux dir -> ended member runs; (3) cross-process stale tmux cache -> 20 s young-run grace. Known/left: trust prompt per member project (Claude doesn't inherit home trust), first-run Bypass consent (by design), 404 /api/pricing/cost for a brand-new session, developer System page shows host paths/DB size. test's workspace #49 + ~/proj kept as a demo. **2026-10-12 v2.7.4 LOCAL (branch release-2.7.4 = c1241123, tag v2.7.4 local only, NOT pushed — push/release was classifier-blocked after the user asked to limit Gitea pushes):** prod :4820 runs it. Added: makeMemberFolder (start makes a missing folder in a member's home, as them), bypass flag only when skipDangerousModePermissionPrompt or a bypass run, developer /api/settings/info + /api/updates/status carry no paths (transcript_cache.keys leaked every user's transcript paths). Verified live: admin Users page (add analyst w/ step-up, EREAUTH, disable/enable, role, ESELF), analyst reads all/writes own, new analyst folder + first Claude session with no dialogs, load 12 viewers p95 ~95 ms 0 errors. Test accounts: testadmin (random pw in ~/.cache/agenthub-e2e/testadmin.cred), testanalyst (.cred there too), test/123456789. Live Update-button test still needs a pushed release newer than prod.