From 0125fcfae8b3bed89209da2cb99cedfeeb1fca82 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 28 Jun 2026 23:41:08 +0200 Subject: [PATCH] feat(#2067): choom --continue passes through to claude --continue (drop --resume translation) --- docs/tools/hivectl-cli.md | 9 +++-- docs/tools/hivectl.md | 18 +++++----- hive-c0re/src/bin/hivectl.rs | 67 +++++++++++++++++++----------------- 3 files changed, 51 insertions(+), 43 deletions(-) diff --git a/docs/tools/hivectl-cli.md b/docs/tools/hivectl-cli.md index 403b7041..03086512 100644 --- a/docs/tools/hivectl-cli.md +++ b/docs/tools/hivectl-cli.md @@ -379,14 +379,17 @@ Generate the federation peer-config block for THIS hive — the nix a peer opera Open an interactive Claude session inside an agent container. -Execs `machinectl shell @h-` running claude as the agent user from its state dir, with the same settings / MCP config / system prompt the harness uses. Without a session id you get a fresh blank session each launch; pass one to resume an earlier choom session (`--resume `). Either way it never collides with the harness's live session — the harness pins its own session id via `--resume`, so a blank choom session is invisible to it. Requires root + a running container. +Execs `machinectl shell @h-` running claude as the agent user from its state dir, with the same settings / MCP config / system prompt the harness uses. Bare `choom ` launches a fresh blank session; `choom --continue ` passes `--continue ` straight through to claude to rejoin a prior session (claude resolves whether the value is a session id or a display name). It never collides with the harness's live session — the harness pins its own session via `--resume`, so a blank choom session is invisible to it. Requires root + a running container. -**Usage:** `hivectl choom [SESSION]` +**Usage:** `hivectl choom [OPTIONS] ` ###### **Arguments:** * `` — Agent name (e.g. `damocles`, `iris`) -* `` — Optional session id to resume (`claude --resume `). Omit for a fresh blank session + +###### **Options:** + +* `--continue ` — Resume a prior claude session by id or display name, passed through as `claude --continue `. Omit for a fresh blank session. A value is required when the flag is given diff --git a/docs/tools/hivectl.md b/docs/tools/hivectl.md index 714eb0ab..9195899c 100644 --- a/docs/tools/hivectl.md +++ b/docs/tools/hivectl.md @@ -127,16 +127,18 @@ running claude from the agent's state dir. Requires root (same as all `machinectl shell` operations). ```bash -hivectl choom iris # fresh blank Claude session in iris's container -hivectl choom iris # resume an earlier choom session +hivectl choom iris # fresh blank Claude session in iris's container +hivectl choom iris --continue # rejoin a prior session by id or display name ``` -With no session id, claude starts a fresh blank session (mints its own -id). Pass a session id to resume it (`claude --resume `); you can -rename it in-session. Either way choom never collides with the harness's -live session in the same project dir: the harness pins its own id via -`--resume`, so a blank choom session is invisible to it. The container -must be running. +Bare `choom` starts a fresh blank session. `--continue ` is +passed straight through as `claude --continue ` to rejoin a +prior session — claude resolves whether the value is a session id or a +display name (you can `/rename` a session in-session for an easy +handle). A value is required when the flag is given. Either way choom +never collides with the harness's live session in the same project dir: +the harness pins its own id via `--resume`, so a blank choom session is +invisible to it. The container must be running. choom reproduces the harness's own claude invocation so the operator lands in a faithful copy of the agent's environment: diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index e348d5a7..6ad8b1b4 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -112,18 +112,21 @@ enum Cmd { /// /// Execs `machinectl shell @h-` running claude as the /// agent user from its state dir, with the same settings / MCP config - /// / system prompt the harness uses. Without a session id you get a - /// fresh blank session each launch; pass one to resume an earlier - /// choom session (`--resume `). Either way it never collides with - /// the harness's live session — the harness pins its own session id - /// via `--resume`, so a blank choom session is invisible to it. - /// Requires root + a running container. + /// / system prompt the harness uses. Bare `choom ` launches a + /// fresh blank session; `choom --continue ` passes + /// `--continue ` straight through to claude to rejoin a prior + /// session (claude resolves whether the value is a session id or a + /// display name). It never collides with the harness's live session — + /// the harness pins its own session via `--resume`, so a blank choom + /// session is invisible to it. Requires root + a running container. Choom { /// Agent name (e.g. `damocles`, `iris`). name: String, - /// Optional session id to resume (`claude --resume `). Omit - /// for a fresh blank session. - session: Option, + /// Resume a prior claude session by id or display name, passed + /// through as `claude --continue `. Omit for a fresh + /// blank session. A value is required when the flag is given. + #[arg(long = "continue", value_name = "SESSION")] + continue_session: Option, }, /// Stop containers hive-wide in one operator action. Bare `hivectl /// stop` stops **everything** — all sub-agents plus the ci, forge, @@ -630,7 +633,10 @@ async fn main() -> Result<()> { Cmd::Subvol { cmd } => match cmd { SubvolCmd::Upgrade { name, yes } => subvol_upgrade(&socket, &name, yes).await, }, - Cmd::Choom { name, session } => choom(&name, session.as_deref()), + Cmd::Choom { + name, + continue_session, + } => choom(&name, continue_session.as_deref()), Cmd::Quota { cmd } => match cmd { QuotaCmd::Enable => quota_enable().await, QuotaCmd::Show { name } => quota_show(name.as_deref()).await, @@ -1034,15 +1040,17 @@ fn agent_exists(name: &str) -> Result { /// 3. **Pass the harness's flags** (`--settings` / `--mcp-config` / /// `--system-prompt-file` from `/run/hive-config/`), each only if its /// file exists so a half-up container degrades to a bare session. -/// 4. **Pick the session explicitly.** With no `session` arg, claude -/// starts a fresh blank session (mints a random id). Pass a session -/// id to resume it (`--resume `); the operator can rename it -/// in-session. Either way choom never clobbers the harness's live -/// session: the harness pins its own id via `--resume`, so a blank -/// choom session in this shared project dir is invisible to it. +/// 4. **Pick the session explicitly.** Bare choom starts a fresh blank +/// session. `--continue ` is passed straight through as +/// `claude --continue ` to rejoin a prior session — claude +/// resolves whether the value is a session id or a display name (the +/// operator can `/rename` a session in-session for an easy handle). +/// Either way choom never clobbers the harness's live session: the +/// harness pins its own id via `--resume`, so a blank choom session +/// in this shared project dir is invisible to it. /// /// Inherits the caller's PTY. Requires root + a running container. -fn choom(name: &str, session: Option<&str>) -> Result<()> { +fn choom(name: &str, continue_session: Option<&str>) -> Result<()> { if !agent_exists(name)? { bail!("no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)"); } @@ -1059,22 +1067,17 @@ fn choom(name: &str, session: Option<&str>) -> Result<()> { // `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"; - // A session id (if given) is resumed via `--resume `; with none, - // claude starts a fresh blank session. The id is interpolated into - // the shell script, so constrain it to a safe charset first to rule - // out injection (real session ids are UUIDs). - let session_arg = match session { - Some(sid) => { - if sid.is_empty() - || !sid - .bytes() - .all(|b| b.is_ascii_alphanumeric() || b"-_.".contains(&b)) - { - bail!( - "invalid session id '{sid}': expected a claude session id (alphanumerics, '-', '_', '.')" - ); + // `--continue ` (if given) is passed straight through to + // claude; with none, claude starts a fresh blank session. The value + // is single-quoted into the shell script, so reject an embedded + // single quote (the only char that breaks single-quoting) to rule + // out injection — ids and display names never contain one. + let session_arg = match continue_session { + Some(val) => { + if val.contains('\'') { + bail!("invalid --continue value '{val}': must not contain a single quote"); } - format!("set -- --resume {sid};") + format!("set -- --continue '{val}';") } None => "set --;".to_string(), };