From 86930f5b176acbcac85ced1e4b4e58bc21b2aeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 30 Jul 2026 04:28:19 +0000 Subject: [PATCH 01/11] rename officer-claude to officer-anthropic-proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the pm2 entry named officer-claude never ran an agent. it starts sidecar/claude/index.ts, which registers as capabilities: ['proxy'] and only holds the anthropic proxy secret and forwards api traffic. the process that actually spawns claude is sidecar/claude/user-instance.ts, which had no pm2 entry at all and was spawned on demand by the main server. that misnomer is how both CLAUDE.md files ended up claiming that restarting officer does not disturb a running agent session. it does: the agent was a grandchild of officer and died with it. correct the name so the next reader starts from a true model, and fix the claim in both files. no behaviour change — officer-agent arrives in the next commit. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 11 +++++++++-- docs/working-on-officer.md | 9 +++++++-- ecosystem.config.cjs | 5 ++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 73323ea1..6d4de067 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,8 +23,15 @@ One Bun process (`src/server.tsx`) serves everything: Long-running and privileged work lives in **sidecars**: separate processes that dial back in over `/api/sidecar/register` and are tracked in `src/servers/sidecar-registry.ts`. PM2 runs them -(`ecosystem.config.cjs`): `officer` (the server), `officer-claude`, `officer-opencode`, -`officer-email`, `officer-pty`, `officer-vnc`. +(`ecosystem.config.cjs`): `officer` (the server), `officer-anthropic-proxy`, `officer-agent`, +`officer-opencode`, `officer-email`, `officer-pty`, `officer-vnc`, `officer-music`, `officer-vault`, +`officer-slskd`. + +**`officer-anthropic-proxy` and `officer-agent` are not the same thing.** The proxy holds the Anthropic +credential and forwards API traffic; the agent is the process that spawns `claude`. They were one entry +named `officer-claude` until the sidecar-isolation work — which is exactly how the false claim that +"restarting officer doesn't disturb the agent" survived so long. Every sidecar is a PM2 peer of +`officer`, so **no sidecar is a child of the server and restarting the server does not kill one.** Agents run **unsandboxed as the server owner**, with `--dangerously-skip-permissions`. This is deliberate — it is the owner's own machine. Do not add a jail without being asked. diff --git a/docs/working-on-officer.md b/docs/working-on-officer.md index c510355a..82b45452 100644 --- a/docs/working-on-officer.md +++ b/docs/working-on-officer.md @@ -57,8 +57,13 @@ Commit messages: simple lowercase, no prefixes, explaining *why*. ## Running and checking your work -The server runs under pm2 as `officer`, plus sidecars (`officer-claude`, `officer-opencode`, -`officer-email`, `officer-pty`, `officer-vnc`). `pm2 list` shows them; `pm2 logs officer` follows. +The server runs under pm2 as `officer`, plus sidecars (`officer-anthropic-proxy`, `officer-agent`, +`officer-opencode`, `officer-email`, `officer-pty`, `officer-vnc`, `officer-music`, `officer-vault`, +`officer-slskd`). `pm2 list` shows them; `pm2 logs officer` follows. + +Two of those names are worth knowing apart: **`officer-anthropic-proxy` holds the Anthropic credential +and proxies API traffic; `officer-agent` is the process that actually runs `claude`.** They used to be +one confusingly-named entry (`officer-claude`) that was only the proxy. **Don't restart the owner's server to test.** Boot your own on a spare port instead — the running instance holds `PORT` from `.env` (9010): diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 15c25a28..c12f1527 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -6,8 +6,11 @@ module.exports = { args: 'start', watch: false, }, + // The Anthropic credential proxy. Despite the old name (`officer-claude`) this process does NOT + // run agents — it holds the proxy secret and forwards to api.anthropic.com. The process that runs + // agents is `officer-agent` below. { - name: 'officer-claude', + name: 'officer-anthropic-proxy', script: 'bun', args: 'run src/servers/sidecar/claude/index.ts', watch: false, From 62dc4c1a5c6dab4b2b9f30a55a10054b9cc18cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Thu, 30 Jul 2026 04:34:01 +0000 Subject: [PATCH 02/11] run the agent as a pm2 peer instead of a child of officer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the process that runs claude (sidecar/claude/user-instance.ts) had no pm2 entry and was spawned on demand by the main server, with stdout/stderr inherited. that made every agent session a grandchild of officer, so pm2's tree-kill took the session down on every `pm2 restart officer` — the single thing that makes it impossible to work on the platform while an agent is running. give it its own entry (officer-agent) and delete the spawn machinery: ensureClaudeSidecar, spawnAndWaitForRegistration, the 50ms registration poll and the per-email claudeProcs/claudeSpawnWaiters maps, ~77 lines. officer now spawns no sidecar at all. for that to work the sidecar had to stop needing officer to start: - it resolves the owner from the database (getOwnerUser) instead of reading CLAUDE_USER_EMAIL out of the env officer built. single-user is a hard invariant, so there is nothing to fan out over. CLAUDE_USER_EMAIL still wins when set, for manual runs, and a fresh install waits for bootstrap rather than exiting into a restart loop. - it reads the anthropic proxy secret from the proxy sidecar's own state file rather than being handed it in env. lazily, because ensureProxySecret persists on a 30s debounce and pm2 starts both processes together. it registers as 'agent' with capability 'claude', so the registry finds it the way it finds every other sidecar. that removes the email argument from killClaude, interruptClaude and clearClaudeSession, which only ever existed to locate a per-email sidecar by name. what officer keeps is a short wait-for-capability, because pm2 brings peers up together and the first request after a boot can beat the sidecar's registration. also align the two officer port fallbacks in the sidecar (5000 for the socket, 9010 for the rest base) — same instance, so they cannot disagree. this fixes R1 and R2 from CLAUDE_SIDECAR_ISOLATION.md. events produced while officer is down are still lost; that is stage 2. Co-Authored-By: Claude Opus 4.8 --- docs/navigation-audit.md | 207 +++++++++++++++++++ ecosystem.config.cjs | 10 + src/databases/officer_db/src/index.ts | 1 + src/databases/officer_db/src/queries/auth.ts | 8 + src/servers/api/chat/websocket.ts | 2 +- src/servers/channels/send-claude-code.ts | 6 +- src/servers/sidecar-registry.ts | 130 +++--------- src/servers/sidecar/claude/state.ts | 25 +++ src/servers/sidecar/claude/user-instance.ts | 73 +++++-- 9 files changed, 340 insertions(+), 122 deletions(-) create mode 100644 docs/navigation-audit.md diff --git a/docs/navigation-audit.md b/docs/navigation-audit.md new file mode 100644 index 00000000..6207f530 --- /dev/null +++ b/docs/navigation-audit.md @@ -0,0 +1,207 @@ +# Frontend Navigation Audit — the "opaque click" anti-pattern + +**Date:** 2026-07-30 · **Branch:** `sidecars-claude` · **Status:** exploration + documentation only (no code changed) + +Prep work for the upcoming **full navigation refactor**. This catalogues every place the frontend +navigates to / selects an **addressable entity** using a `