fix: hivectl choom passes the harness --settings/--mcp-config/--system-prompt-file so claude gets settings + tools + persona

This commit is contained in:
damocles 2026-06-05 22:16:00 +02:00 committed by mara
commit 07e8f442cb
3 changed files with 64 additions and 28 deletions

View file

@ -80,12 +80,14 @@ enum Cmd {
/// root (same as all machinectl shell operations) and the container
/// must be running.
///
/// The session is entered **as the agent user** (not root): claude
/// reads its OAuth credentials + settings from the agent's
/// `/home/<name>/.claude`, and the working directory is set to the
/// agent's state dir (`/agents/<name>/state`) so `--continue`
/// resumes the same per-project session the harness runs. Entering
/// as root (the `machinectl shell` default) is what loses both.
/// To match the harness exactly, choom enters **as the agent user**
/// (not root) so claude reads the OAuth credentials from the agent's
/// `/home/<name>/.claude`; runs from the agent's state dir
/// (`/agents/<name>/state`) so `--continue` resumes the right
/// per-project session and `CLAUDE.md` loads; and passes the same
/// `--settings` / `--mcp-config` / `--system-prompt-file` the harness
/// writes to `/run/hive-config/` (settings, MCP tools, role prompt).
/// Entering as root (the `machinectl shell` default) loses all of it.
///
/// Pass `--fresh` to start a new Claude session instead of continuing
/// the most recent one.
@ -366,23 +368,35 @@ fn is_agent(name: &str) -> bool {
/// Drop into an interactive Claude session in the agent container.
///
/// Replaces the current process (exec) with `machinectl shell
/// <name>@h-<name> /bin/sh -lc 'cd /agents/<name>/state && exec claude
/// [--continue]'`. Two things matter here, both of which the naive
/// <name>@h-<name> /bin/sh -lc '<script>'`, where the script `cd`s into
/// the agent's state dir and execs claude with the *same* flags the
/// harness uses. Three things matter here, all of which the naive
/// `machinectl shell h-<name> claude` got wrong (issue: choom missing
/// creds + settings):
///
/// 1. **Run as the agent user.** `machinectl shell` defaults to root in
/// the container, so claude would read `/root/.claude` (empty)
/// instead of the agent's `/home/<name>/.claude` where the OAuth
/// credentials + settings live. Prefixing the machine with
/// `<name>@` enters the session as the agent user (the meta-flake
/// sets `hyperhive.user.name` to the agent label, so the unix user
/// name matches the agent name).
/// credentials live. Prefixing the machine with `<name>@` enters the
/// session as the agent user (the meta-flake sets
/// `hyperhive.user.name` to the agent label, so the unix user name
/// matches the agent name).
/// 2. **Start in the agent's state dir.** `claude --continue` resumes
/// the most recent session *for the current project directory*. The
/// harness runs claude from `/agents/<name>/state`, so choom has to
/// `cd` there or `--continue` finds no session (it would look under
/// the user's home instead).
/// the most recent session *for the current project directory*, and
/// project memory (`CLAUDE.md`) is read from the cwd. The harness
/// runs claude from `/agents/<name>/state`, so choom has to `cd`
/// there or `--continue` finds no session and the persona doesn't
/// load.
/// 3. **Pass the harness's claude flags.** The harness drops
/// `claude-{settings.json,mcp-config.json,system-prompt.md}` into
/// `/run/hive-config/` (`hive-ag3nt::paths::config_dir()`) and runs
/// claude with `--settings` / `--mcp-config` / `--system-prompt-file`
/// pointing at them. A bare `claude --continue` skips all three, so
/// the operator gets default settings, no hyperhive/matrix MCP tools
/// (which `--continue` needs to replay a tool-using history), and no
/// role prompt. choom now mirrors those flags. Each is added only if
/// the file exists, so a mid-restart container degrades to a bare
/// session instead of erroring.
///
/// `machinectl shell` inherits the caller's PTY, so the session is
/// fully interactive. Requires root and a running container.
@ -399,11 +413,24 @@ fn choom(name: &str, fresh: bool) -> Result<()> {
// In-container state dir (host `/var/lib/hyperhive/agents/<name>/state`
// is bind-mounted here); matches `hive-ag3nt::paths::state_dir()`.
let state_dir = format!("/agents/{name}/state");
// Per-turn config dir the harness writes (RuntimeDirectory
// `hive-config`); matches `hive-ag3nt::paths::config_dir()`. These
// are the exact files the harness passes to claude each turn.
let cfg = "/run/hive-config";
let continue_flag = if fresh { "" } else { " --continue" };
// `cd` so `--continue` resolves the harness's per-project session;
// `exec` replaces the shell with claude so it owns the PTY directly.
// `name` is constrained to `[a-z0-9._-]` so there's nothing to quote.
let inner = format!("cd {state_dir} && exec {claude}{continue_flag}");
// Build the claude argv as the harness does, but only include each
// flag when its file is present so a half-up container falls back to
// a bare session rather than a hard claude error. `name` is
// constrained to `[a-z0-9._-]` so there's nothing to quote.
// `cd` so `--continue` + CLAUDE.md resolve against the agent's
// project; `exec` hands the PTY to claude directly.
let inner = format!(
"cd {state_dir} || exit 1; set --; \
[ -f {cfg}/claude-settings.json ] && set -- \"$@\" --settings {cfg}/claude-settings.json; \
[ -f {cfg}/claude-mcp-config.json ] && set -- \"$@\" --mcp-config {cfg}/claude-mcp-config.json; \
[ -f {cfg}/claude-system-prompt.md ] && set -- \"$@\" --system-prompt-file {cfg}/claude-system-prompt.md; \
exec {claude} \"$@\"{continue_flag}"
);
let mut cmd = std::process::Command::new("machinectl");
cmd.arg("shell")
.arg(&target)