From ea5f70629c1185436c0f8941375361d45b741933 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 14:40:46 +0200 Subject: [PATCH] harness-base: generic bash-env.sh + _bashEnvFragments accumulator (mara on #779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mara: 'if we replace it with one thing, that should be named more generic so we dont have to change it for future additions'. extract the BASH_ENV plumbing into a shared shape: - new internal option `hyperhive._bashEnvFragments` (types.lines) accumulates shell snippets across feature modules. - file path is now `/etc/hyperhive/bash-env.sh` (was the cargo-specific bash-cargo-short.sh). - the file + BASH_ENV + interactiveShellInit are gated on `_bashEnvFragments != """ so a fully feature-disabled agent has no overhead. cargo function moves to a `lib.mkIf cargo.shortMessages` contribution to `_bashEnvFragments` — same behaviour, no rename when the next hook (nix-env helper, claude-cmd helpers, whatever) lands. --- nix/templates/harness-base.nix | 149 ++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index ca02087f..e2ba4a3a 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -537,6 +537,30 @@ in ''; }; + # Internal accumulator for shell snippets that should land in + # `/etc/hyperhive/bash-env.sh`. Per-feature hooks set this via + # `lib.mkIf` gated on their own option; the lines type merges + # all contributions across modules into one file. Loaded via + # `$BASH_ENV` for non-interactive shells (claude's `Bash` tool + # runs `bash -c`) and via `programs.bash.interactiveShellInit` + # for interactive shells. Generic by design (mara on #779) so + # future hooks don't need to either rename this file or invent + # a parallel dispatcher. + options.hyperhive._bashEnvFragments = lib.mkOption { + type = lib.types.lines; + default = ""; + internal = true; + description = '' + Shell snippets concatenated into `/etc/hyperhive/bash-env.sh`. + Feature hooks contribute via `lib.mkIf` gated on their own + option. When empty, the file isn't created, `BASH_ENV` stays + unset, and the interactive bashrc hook is omitted — zero cost + when no feature is on. Internal — set indirectly via the + per-feature options that own the gate (e.g. + `hyperhive.cargo.shortMessages`). + ''; + }; + options.hyperhive.cargo.shortMessages = lib.mkOption { type = lib.types.bool; default = true; @@ -550,12 +574,12 @@ in the response window with per-crate progress lines that carry no signal beyond the warning/error summary (#777). - Implementation: a `cargo` shell function defined in - `/etc/hyperhive/bash-cargo-short.sh`. Loaded via `BASH_ENV` - for non-interactive shells (`bash -c` — what the claude - `Bash` tool runs) and sourced from `programs.bash.interactiveShellInit` - for interactive shells (operator pokes around inside the - container). The function: + Implementation: contributes a `cargo` shell function to + `/etc/hyperhive/bash-env.sh` (see `hyperhive._bashEnvFragments`). + Loaded via `BASH_ENV` for non-interactive shells (`bash -c` — + what the claude `Bash` tool runs) and sourced from + `programs.bash.interactiveShellInit` for interactive shells. + The function: - handles the `+toolchain` selector prefix (`cargo +nightly build` works); @@ -746,50 +770,55 @@ in source = config.hyperhive.icon; }; - # Cargo `--message-format short` injector (#777). Sourced from - # BASH_ENV in non-interactive shells AND interactiveShellInit - # so both claude's `Bash` tool and operator SSH sessions get - # the same compact compile output. `command cargo …` falls back - # to the un-wrapped binary in PATH (the rust toolchain's cargo - # — either from `environment.systemPackages` or from whatever - # `nix develop` shell the agent's working in). - environment.etc."hyperhive/bash-cargo-short.sh" = - lib.mkIf config.hyperhive.cargo.shortMessages - { - text = '' - # Auto-injects --message-format short on cargo compile - # subcommands so per-crate progress lines don't flood - # claude's context (#777). Bypassed when the caller - # already passes --message-format (any form). - cargo() { - # Strip leading +toolchain selectors (cargo +nightly …). - local pre=() - while [ "''${1:0:1}" = "+" ] && [ -n "''${1:-}" ]; do - pre+=("$1") - shift - done - case "''${1:-}" in - build|check|clippy|test|run|doc|bench|install|rustc|fix) - local sub="$1" - shift - local arg - for arg in "$@"; do - case "$arg" in - --message-format|--message-format=*) - command cargo "''${pre[@]}" "$sub" "$@" - return $? - ;; - esac - done - command cargo "''${pre[@]}" "$sub" --message-format short "$@" - ;; - *) - command cargo "''${pre[@]}" "$@" + # Cargo `--message-format short` injector (#777). Contributes a + # `cargo` shell function to `hyperhive._bashEnvFragments`; the + # bash-env infrastructure below packages that into a single file + # sourced by both non-interactive and interactive shells. + # `command cargo …` falls back to the un-wrapped binary in PATH + # (the rust toolchain's cargo — either from `environment.systemPackages` + # or from whatever `nix develop` shell the agent's working in). + hyperhive._bashEnvFragments = lib.mkIf config.hyperhive.cargo.shortMessages '' + # Auto-injects --message-format short on cargo compile + # subcommands so per-crate progress lines don't flood + # claude's context (#777). Bypassed when the caller + # already passes --message-format (any form). + cargo() { + # Strip leading +toolchain selectors (cargo +nightly …). + local pre=() + while [ "''${1:0:1}" = "+" ] && [ -n "''${1:-}" ]; do + pre+=("$1") + shift + done + case "''${1:-}" in + build|check|clippy|test|run|doc|bench|install|rustc|fix) + local sub="$1" + shift + local arg + for arg in "$@"; do + case "$arg" in + --message-format|--message-format=*) + command cargo "''${pre[@]}" "$sub" "$@" + return $? ;; esac - } - ''; - }; + done + command cargo "''${pre[@]}" "$sub" --message-format short "$@" + ;; + *) + command cargo "''${pre[@]}" "$@" + ;; + esac + } + ''; + + # Single bash-env file with all configured shell fragments. + # Wiring is gated on at least one fragment being active so a + # fully feature-disabled agent has neither the file nor the + # `BASH_ENV` / interactive sourcing — zero cost in that case. + environment.etc."hyperhive/bash-env.sh" = + lib.mkIf (config.hyperhive._bashEnvFragments != "") { + text = config.hyperhive._bashEnvFragments; + }; environment.etc."hyperhive/bash-allow.json".text = builtins.toJSON config.hyperhive.allowedBashPatterns; @@ -853,23 +882,25 @@ in // lib.optionalAttrs (config.hyperhive.forge.skipNotifyReasons != [ ]) { HIVE_FORGE_NOTIFY_SKIP_REASONS = lib.concatStringsSep "," config.hyperhive.forge.skipNotifyReasons; } - // lib.optionalAttrs config.hyperhive.cargo.shortMessages { + // lib.optionalAttrs (config.hyperhive._bashEnvFragments != "") { # Non-interactive bash invocations (claude's `Bash` tool runs - # `bash -c`) source $BASH_ENV at startup — drops the cargo - # function defined in the file above into scope without - # touching /etc/profile (login-only). Interactive shells - # source the same file via the interactiveShellInit hook - # below so behaviour matches across both modes (#777). - BASH_ENV = "/etc/hyperhive/bash-cargo-short.sh"; + # `bash -c`) source $BASH_ENV at startup — drops every active + # feature hook's snippet into scope without touching + # `/etc/profile` (login-only). Interactive shells source the + # same file via the `interactiveShellInit` hook below so + # behaviour matches across both modes (#777). + BASH_ENV = "/etc/hyperhive/bash-env.sh"; }; # Interactive shells don't honour BASH_ENV — wire the same file # in via the bashrc hook so operator SSH sessions get the same - # short-format cargo output as claude's non-interactive calls. + # hook surface as claude's non-interactive calls. Gated on at + # least one fragment being active so we don't write a no-op + # source line into `/etc/bashrc` on fully-feature-disabled agents. programs.bash.interactiveShellInit = - lib.mkIf config.hyperhive.cargo.shortMessages '' - if [ -r /etc/hyperhive/bash-cargo-short.sh ]; then - . /etc/hyperhive/bash-cargo-short.sh + lib.mkIf (config.hyperhive._bashEnvFragments != "") '' + if [ -r /etc/hyperhive/bash-env.sh ]; then + . /etc/hyperhive/bash-env.sh fi '';