From 9f1b20f9a1c81105766f0d1191a859ab1e6d87ea Mon Sep 17 00:00:00 2001 From: junior Date: Wed, 15 Jul 2026 21:39:50 -0400 Subject: [PATCH 1/5] fix: preserve harness child boundary --- .mise/tasks/run | 40 ++++---- CONTRIBUTING.md | 1 + README.md | 2 + README.tsx | 6 ++ cli/lib/cli.ex | 5 +- cli/lib/cli/engine.ex | 12 ++- cli/lib/harness/pi/command.ex | 29 +++++- cli/test/engine_test.exs | 138 ++++++++++++++++++++++----- cli/test/harness/pi/command_test.exs | 89 ++++++++++------- lib/harness-env.sh | 14 ++- lib/harness/claude.sh | 6 ++ lib/harness/pi.sh | 33 +++++++ test/helpers.bash | 16 ++-- test/ps.bats | 2 +- test/run.bats | 35 ++++--- test/wake.bats | 2 +- 16 files changed, 320 insertions(+), 110 deletions(-) diff --git a/.mise/tasks/run b/.mise/tasks/run index 6a655f1..a2504b6 100755 --- a/.mise/tasks/run +++ b/.mise/tasks/run @@ -59,6 +59,8 @@ source "$MISE_CONFIG_ROOT/lib/processes.sh" PROMPT_TEMP_FILE="" PROCESS_START_ID="" +HARNESS_NAME="" +HARNESS_EXECUTABLE="" # shellcheck disable=SC2329 # Invoked by EXIT trap below. cleanup_prompt_temp() { if [ -n "$PROMPT_TEMP_FILE" ]; then @@ -234,6 +236,18 @@ fi sessions_validate_project_trust "$PROJECT_TRUST" || exit 1 +# Resolve the selected harness and its Sessions-owned executable before any +# launch environment is sanitized. Unsupported skeleton adapters may return no +# executable and retain their adapter-level rejection in the Elixir boundary. +if [ -n "$SESSION" ]; then + HARNESS_NAME=$(harness_resolve --session "$SESSION") || exit 1 +else + HARNESS_NAME=$(harness_resolve) || exit 1 +fi +# shellcheck source=/dev/null +source "$MISE_CONFIG_ROOT/lib/harness/$HARNESS_NAME.sh" +HARNESS_EXECUTABLE=$(harness_call "$HARNESS_NAME" executable "$MISE_CONFIG_ROOT") || exit 1 + # Prompt resolution order: # 1. Explicit --system-prompt-file # 2. Latest system_prompt entry baked into --session by `sessions new` @@ -284,33 +298,16 @@ HEADLESS_EOF fi run_interactive() { - # shellcheck source=../../lib/harness/dispatch.sh - source "$MISE_CONFIG_ROOT/lib/harness/dispatch.sh" - - local harness_name - if [ -n "$SESSION" ]; then - harness_name=$(harness_resolve --session "$SESSION") || exit 1 - else - harness_name=$(harness_resolve) || exit 1 - fi - # shellcheck source=/dev/null - source "$MISE_CONFIG_ROOT/lib/harness/$harness_name.sh" - - if [ "$harness_name" != "pi" ]; then - echo "sessions: '$harness_name' harness does not support interactive no-message run yet" >&2 + if [ "$HARNESS_NAME" != "pi" ]; then + echo "sessions: '$HARNESS_NAME' harness does not support interactive no-message run yet" >&2 exit "$HARNESS_UNSUPPORTED_EXIT" fi sessions_prepare_harness_env - local -a pi_command=(mise -C "$MISE_CONFIG_ROOT" exec -- pi) - if ! command -v mise >/dev/null 2>&1; then - echo "Error: mise not found on PATH for sessions-owned pi harness" >&2 - exit 1 - fi - + local -a pi_command=("$HARNESS_EXECUTABLE") local project_trust_flag - project_trust_flag=$(harness_call "$harness_name" project_trust_flag "$PROJECT_TRUST") || exit 1 + project_trust_flag=$(harness_call "$HARNESS_NAME" project_trust_flag "$PROJECT_TRUST") || exit 1 local pi_args=(--model "$MODEL") [ -n "$project_trust_flag" ] && pi_args+=("$project_trust_flag") @@ -354,6 +351,7 @@ CLI_ARGS=(--cwd "$CWD") [ -n "$TIMEOUT" ] && CLI_ARGS+=(--timeout "$TIMEOUT") CLI_ARGS+=(--model "$MODEL") [ -n "$SESSION" ] && CLI_ARGS+=(--session "$SESSION") +[ -n "$HARNESS_EXECUTABLE" ] && CLI_ARGS+=(--harness-executable "$HARNESS_EXECUTABLE") CLI_ARGS+=(--project-trust "$PROJECT_TRUST") [ "$EXTENSIONS" != "true" ] && CLI_ARGS+=(--no-extensions) [ "$SKILLS" != "true" ] && CLI_ARGS+=(--no-skills) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7008edb..2dce80b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,7 @@ Keep these generic in core task/library code: Keep harness-specific knowledge in `lib/harness/.sh` and `lib/harness/.py`: +- exact executable resolution from Sessions' declared tool context; - session file locations and path encoding; - native JSONL message schemas; - launch arguments for a harness binary; diff --git a/README.md b/README.md index 4e4fd5c..401d079 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ sessions wake review/pr-50 --model openai-codex/gpt-5.5 --message "You missed th The spawning stack uses [shell](https://github.com/KnickKnackLabs/shell) for persistent zmx sessions. `sessions wake` calls `sessions run` as its hidden low-level executor. For profile-specific sessions, use `new` + `wake`: bake profile or task instructions into the session with `--system-prompt-file` at creation, then wake it with task messages. If no explicit or baked prompt exists, the harness starts without an appended prompt and can rely on its native cwd context discovery. +Before launch, Sessions resolves the selected harness executable from its own declared toolchain. The child then starts in the requested `--cwd` with Sessions' mise task context and direct tool-install paths removed. This keeps the harness version pinned without replacing the target project or agent home's own tool and resource context. + `sessions run` remains available as an advanced/compatibility command. It accepts an explicit `--system-prompt-file`, uses any prompt baked into the session, and otherwise starts without appending a system prompt. Caller-provided context belongs to the caller, not to `sessions`. `--model` on `sessions wake` is required and is not remembered across wakes — pass a provider-qualified model (for example `openai-codex/gpt-5.5`) on each wake. diff --git a/README.tsx b/README.tsx index 175ae5c..17bcfd3 100644 --- a/README.tsx +++ b/README.tsx @@ -232,6 +232,12 @@ sessions wake review/pr-50 --model openai-codex/gpt-5.5 --message "You missed th {" at creation, then wake it with task messages. If no explicit or baked prompt exists, the harness starts without an appended prompt and can rely on its native cwd context discovery."} + + {"Before launch, Sessions resolves the selected harness executable from its own declared toolchain. The child then starts in the requested "} + --cwd + {" with Sessions' mise task context and direct tool-install paths removed. This keeps the harness version pinned without replacing the target project or agent home's own tool and resource context."} + + sessions run {" remains available as an advanced/compatibility command. It accepts an explicit "} diff --git a/cli/lib/cli.ex b/cli/lib/cli.ex index f959b72..c8be92c 100644 --- a/cli/lib/cli.ex +++ b/cli/lib/cli.ex @@ -55,6 +55,7 @@ defmodule Cli do skills = opts[:no_skills] != true prompt_templates = opts[:no_prompt_templates] != true project_trust = opts[:project_trust] || "inherit" + harness_executable = opts[:harness_executable] print_header(opts, message, timeout, model) @@ -75,7 +76,8 @@ defmodule Cli do extensions: extensions, skills: skills, prompt_templates: prompt_templates, - project_trust: project_trust + project_trust: project_trust, + harness_executable: harness_executable ) end end @@ -129,6 +131,7 @@ defmodule Cli do no_skills: :boolean, no_prompt_templates: :boolean, project_trust: :string, + harness_executable: :string, help: :boolean ], aliases: [h: :help] diff --git a/cli/lib/cli/engine.ex b/cli/lib/cli/engine.ex index 0ee5655..d63783f 100644 --- a/cli/lib/cli/engine.ex +++ b/cli/lib/cli/engine.ex @@ -20,7 +20,8 @@ defmodule Cli.Engine do extensions: boolean(), skills: boolean(), prompt_templates: boolean(), - project_trust: String.t() + project_trust: String.t(), + harness_executable: String.t() | nil ] @doc """ @@ -91,13 +92,16 @@ defmodule Cli.Engine do end defp harness_env do - caller_pwd_env_scrub() ++ path_env() + inherited_task_env_scrub() ++ path_env() end - defp caller_pwd_env_scrub do + defp inherited_task_env_scrub do System.get_env() |> Map.keys() - |> Enum.filter(&(&1 == "CALLER_PWD" or String.ends_with?(&1, "_CALLER_PWD"))) + |> Enum.filter(fn name -> + name == "CALLER_PWD" or String.ends_with?(name, "_CALLER_PWD") or + String.starts_with?(name, "MISE_") or String.starts_with?(name, "usage_") + end) |> Enum.map(&{String.to_charlist(&1), false}) end diff --git a/cli/lib/harness/pi/command.ex b/cli/lib/harness/pi/command.ex index 76f8d6b..9fdcaae 100644 --- a/cli/lib/harness/pi/command.ex +++ b/cli/lib/harness/pi/command.ex @@ -3,9 +3,9 @@ defmodule Cli.Harness.Pi.Command do Pi command construction — builds the shell invocation passed to the port under `/bin/sh -c