diff --git a/bin/backends/herdr.sh b/bin/backends/herdr.sh index b836b77201e..4050626b3d2 100644 --- a/bin/backends/herdr.sh +++ b/bin/backends/herdr.sh @@ -1652,15 +1652,28 @@ fm_backend_herdr_projection_order_best_effort() { # - local session=$1 running out i + local session=$1 running out i client_bin running=$(fm_backend_herdr_cli "$session" status --json 2>/dev/null | jq -r '.server.running // false' 2>/dev/null) [ "$running" = "true" ] && return 0 ( unset FM_HOME FM_ROOT_OVERRIDE FM_STATE_OVERRIDE FM_DATA_OVERRIDE FM_PROJECTS_OVERRIDE FM_CONFIG_OVERRIDE \ CURSOR_AGENT CURSOR_INVOKED_AS CLAUDECODE PI_CODING_AGENT FM_PI_HARNESS GROK_AGENT FM_SUPERVISION_MODEL - fm_backend_herdr_cli "$session" server >/dev/null 2>&1 & + if command -v setsid >/dev/null 2>&1; then + # Match fm_backend_herdr_cli's session-scoped client selection. setsid + # needs an executable, not that shell function; util-linux forks if the + # launcher is already a process-group leader (setsid(2) would fail). + client_bin=herdr + if [ "${FM_BACKEND_HERDR_CLIENT_SESSION:-}" = "$session" ]; then + client_bin=$(fm_backend_herdr_bin) + fi + HERDR_SESSION="$session" setsid "$client_bin" server --session "$session" >/dev/null 2>&1 & + else + fm_backend_herdr_cli "$session" server >/dev/null 2>&1 & + fi ) || return 1 for i in $(seq 1 20); do running=$(fm_backend_herdr_cli "$session" status --json 2>/dev/null | jq -r '.server.running // false' 2>/dev/null) diff --git a/docs/herdr-backend.md b/docs/herdr-backend.md index ee944fdf1b1..30626f0d77c 100644 --- a/docs/herdr-backend.md +++ b/docs/herdr-backend.md @@ -219,10 +219,13 @@ Workspace and tab ids support verification and cleanup but are not inferred from ## Current transport behavior The adapter starts and polls a named server before workspace, tab, pane, or agent calls. -Every Herdr invocation goes through `fm_backend_herdr_cli`, which sets the environment and passes an explicit trailing `--session `. +Every Herdr invocation except the detached server launch below goes through `fm_backend_herdr_cli`, which sets the environment and passes an explicit trailing `--session `. An environment variable alone is not reliable when another Herdr server is running. When the selected named server is not running, the adapter launches it without inherited Firstmate home and directory overrides, harness identity markers, or the supervision-model override. Herdr passes its server startup environment to every later pane, so retaining those values could misroute panes for another Firstmate home or harness. +Where `setsid(1)` is available, that launch detaches the server into its own POSIX session: Herdr computes `capabilities.detached_server_daemon` once at server start from whether the process leads its own session, and a server without it refuses saved machines. +Because `setsid` needs an executable rather than the CLI helper, the detached launch runs the client this session already selected, with the same explicit `--session ` routing. +A host without that utility, such as macOS, keeps the plain background launch through the helper. An already-running server is reused without restart or environment changes. Explicit named-session routing and unrelated launch environment remain intact. diff --git a/tests/fm-backend-herdr.test.sh b/tests/fm-backend-herdr.test.sh index d8692fcf3d8..a3f14f8ad96 100755 --- a/tests/fm-backend-herdr.test.sh +++ b/tests/fm-backend-herdr.test.sh @@ -101,6 +101,9 @@ case "${1:-}" in printf '%s=%s\n' "$name" "$value" done printf 'args=%s\n' "$*" + if command -v setsid >/dev/null 2>&1; then + ps -o sid=,pid= -p "$$" | awk '{printf "sid=%s pid=%s\n", $1, $2}' + fi } > "$FM_HERDR_SERVER_ENV_LOG" : > "$FM_HERDR_SERVER_MARKER" ;; @@ -1101,6 +1104,13 @@ test_server_ensure_scrubs_home_and_harness_identity() { assert_contains "$output" "FM_HERDR_SENTINEL=kept" "server_ensure removed an unrelated environment variable" assert_contains "$output" "HERDR_SESSION=fmtest" "server_ensure lost explicit Herdr session routing" assert_contains "$output" "args=server --session fmtest" "server_ensure lost the trailing Herdr session flag" + if command -v setsid >/dev/null 2>&1; then + assert_contains "$output" "sid=" "server_ensure did not record the server's POSIX session" + local sid pid + sid=$(printf '%s\n' "$output" | sed -n 's/^sid=\([0-9]*\) pid=[0-9]*$/\1/p') + pid=$(printf '%s\n' "$output" | sed -n 's/^sid=[0-9]* pid=\([0-9]*\)$/\1/p') + [ -n "$pid" ] && [ "$sid" = "$pid" ] || fail "server_ensure launched the server outside its own POSIX session (sid=$sid pid=$pid)" + fi pass "fm_backend_herdr_server_ensure: scrubs home and harness identity without disturbing unrelated environment or session routing" }