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

@ -281,7 +281,7 @@ Open an interactive Claude session inside an agent container.
Replaces the current process with `machinectl shell <name>@h-<name>` running `claude --continue` from the agent's state dir — drops the operator straight into the agent's live Claude session with its full loaded context and persona. Requires root (same as all machinectl shell operations) and the container must be running. Replaces the current process with `machinectl shell <name>@h-<name>` running `claude --continue` from the agent's state dir — drops the operator straight into the agent's live Claude session with its full loaded context and persona. Requires 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. Pass `--fresh` to start a new Claude session instead of continuing the most recent one.

View file

@ -124,10 +124,19 @@ Without `--fresh`, `--continue` is passed so the operator joins the
agent's live context window. With `--fresh` a clean session starts. agent's live context window. With `--fresh` a clean session starts.
The container must be running. The container must be running.
The session is entered **as the agent user**, not root: `machinectl choom reproduces the harness's own claude invocation so the operator
shell` defaults to root in the container, which would make claude read lands in a faithful copy of the agent's environment:
`/root/.claude` (empty) instead of the agent's `/home/<name>/.claude`
where its OAuth credentials + settings live. choom prefixes the - **as the agent user**, not root: `machinectl shell` defaults to root,
machine with `<name>@` (the meta-flake sets the agent's unix user name which would make claude read `/root/.claude` (empty) instead of the
to its label) and `cd`s into `/agents/<name>/state` first so agent's `/home/<name>/.claude` where its OAuth credentials live. choom
`--continue` resolves the same per-project session the harness runs. prefixes the machine with `<name>@` (the meta-flake sets the agent's
unix user name to its label).
- **from `/agents/<name>/state`**: `--continue` resolves the session for
that project dir and `CLAUDE.md` (the persona) loads from the cwd.
- **with the harness flags**: `--settings`, `--mcp-config`, and
`--system-prompt-file` from `/run/hive-config/` — the same files the
harness writes each turn — so the operator gets the agent's settings,
the hyperhive/matrix MCP tools (which `--continue` needs to replay a
tool-using history), and the role prompt. Each flag is included only
when its file exists.

View file

@ -80,12 +80,14 @@ enum Cmd {
/// root (same as all machinectl shell operations) and the container /// root (same as all machinectl shell operations) and the container
/// must be running. /// must be running.
/// ///
/// The session is entered **as the agent user** (not root): claude /// To match the harness exactly, choom enters **as the agent user**
/// reads its OAuth credentials + settings from the agent's /// (not root) so claude reads the OAuth credentials from the agent's
/// `/home/<name>/.claude`, and the working directory is set to the /// `/home/<name>/.claude`; runs from the agent's state dir
/// agent's state dir (`/agents/<name>/state`) so `--continue` /// (`/agents/<name>/state`) so `--continue` resumes the right
/// resumes the same per-project session the harness runs. Entering /// per-project session and `CLAUDE.md` loads; and passes the same
/// as root (the `machinectl shell` default) is what loses both. /// `--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 /// Pass `--fresh` to start a new Claude session instead of continuing
/// the most recent one. /// the most recent one.
@ -366,23 +368,35 @@ fn is_agent(name: &str) -> bool {
/// Drop into an interactive Claude session in the agent container. /// Drop into an interactive Claude session in the agent container.
/// ///
/// Replaces the current process (exec) with `machinectl shell /// Replaces the current process (exec) with `machinectl shell
/// <name>@h-<name> /bin/sh -lc 'cd /agents/<name>/state && exec claude /// <name>@h-<name> /bin/sh -lc '<script>'`, where the script `cd`s into
/// [--continue]'`. Two things matter here, both of which the naive /// 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 /// `machinectl shell h-<name> claude` got wrong (issue: choom missing
/// creds + settings): /// creds + settings):
/// ///
/// 1. **Run as the agent user.** `machinectl shell` defaults to root in /// 1. **Run as the agent user.** `machinectl shell` defaults to root in
/// the container, so claude would read `/root/.claude` (empty) /// the container, so claude would read `/root/.claude` (empty)
/// instead of the agent's `/home/<name>/.claude` where the OAuth /// instead of the agent's `/home/<name>/.claude` where the OAuth
/// credentials + settings live. Prefixing the machine with /// credentials live. Prefixing the machine with `<name>@` enters the
/// `<name>@` enters the session as the agent user (the meta-flake /// session as the agent user (the meta-flake sets
/// sets `hyperhive.user.name` to the agent label, so the unix user /// `hyperhive.user.name` to the agent label, so the unix user name
/// name matches the agent name). /// matches the agent name).
/// 2. **Start in the agent's state dir.** `claude --continue` resumes /// 2. **Start in the agent's state dir.** `claude --continue` resumes
/// the most recent session *for the current project directory*. The /// the most recent session *for the current project directory*, and
/// harness runs claude from `/agents/<name>/state`, so choom has to /// project memory (`CLAUDE.md`) is read from the cwd. The harness
/// `cd` there or `--continue` finds no session (it would look under /// runs claude from `/agents/<name>/state`, so choom has to `cd`
/// the user's home instead). /// 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 /// `machinectl shell` inherits the caller's PTY, so the session is
/// fully interactive. Requires root and a running container. /// 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` // In-container state dir (host `/var/lib/hyperhive/agents/<name>/state`
// is bind-mounted here); matches `hive-ag3nt::paths::state_dir()`. // is bind-mounted here); matches `hive-ag3nt::paths::state_dir()`.
let state_dir = format!("/agents/{name}/state"); 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" }; let continue_flag = if fresh { "" } else { " --continue" };
// `cd` so `--continue` resolves the harness's per-project session; // Build the claude argv as the harness does, but only include each
// `exec` replaces the shell with claude so it owns the PTY directly. // flag when its file is present so a half-up container falls back to
// `name` is constrained to `[a-z0-9._-]` so there's nothing to quote. // a bare session rather than a hard claude error. `name` is
let inner = format!("cd {state_dir} && exec {claude}{continue_flag}"); // 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"); let mut cmd = std::process::Command::new("machinectl");
cmd.arg("shell") cmd.arg("shell")
.arg(&target) .arg(&target)