From 580e52896375078b3e2f2c37f5554915ebb8a975 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:03:24 +0100 Subject: [PATCH 1/7] mason: add runon macos demand for MOTOR macOS VMs Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit 1bef6ae81c851c26de7b7f06f0c2acd580f73b99) --- .alfonso/release-notes/next/runon-macos.md | 1 + .../aft/src/bash_background/remote_tests.rs | 27 ++++++++++- crates/aft/src/exec_remote/policy.rs | 47 ++++++++++++++++++- crates/aft/src/subc_tool_remote_schemas.json | 4 +- .../tool_provider_catalog_presets.json | 4 +- docs/tools.md | 9 ++-- packages/aft-bridge/src/bash-host-fallback.ts | 2 +- .../__tests__/subc-tool-schemas-fresh.test.ts | 2 +- 8 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 .alfonso/release-notes/next/runon-macos.md diff --git a/.alfonso/release-notes/next/runon-macos.md b/.alfonso/release-notes/next/runon-macos.md new file mode 100644 index 0000000000..34c8ee52f7 --- /dev/null +++ b/.alfonso/release-notes/next/runon-macos.md @@ -0,0 +1 @@ +- `runon: "macos"` sends the command to a macOS machine for builds and tests that need macOS instead of running them on this machine. diff --git a/crates/aft/src/bash_background/remote_tests.rs b/crates/aft/src/bash_background/remote_tests.rs index 05e332aaca..4fe49bb840 100644 --- a/crates/aft/src/bash_background/remote_tests.rs +++ b/crates/aft/src/bash_background/remote_tests.rs @@ -5737,6 +5737,26 @@ async fn runon_sends_the_whole_compound_line_remote_exactly_as_written() { ); } +#[tokio::test] +async fn runon_macos_reaches_the_wire_request_as_macos_platform() { + let daemon = daemon(Script::Plain, "exec-remote/v1").await; + let dir = tempfile::tempdir().unwrap(); + let ctx = restarted_context(dir.path()); + let response = handle_with_policy( + &ctx, + Some(launch(daemon.connection.clone())), + serde_json::json!({"command": "uname -a", "runon": "macos", "compressed": false}), + ); + assert!(response.success, "{response:?}"); + let task_id = response.data["task_id"].as_str().unwrap().to_string(); + let _done = terminal(ctx.bash_background(), &task_id).await; + let runs = exec_runs(&daemon); + assert_eq!(runs.len(), 1, "{runs:?}"); + assert_eq!(runs[0]["platform"], "macos"); + assert!(runs[0].get("weight_hint").is_none(), "{runs:?}"); + assert!(runs[0].get("network").is_none(), "{runs:?}"); +} + #[tokio::test] async fn runon_vcpu_count_is_sent_as_weight_hint_on_the_wire() { let daemon = daemon(Script::Plain, "exec-remote/v1").await; @@ -5798,6 +5818,9 @@ async fn malformed_runon_vcpu_requests_are_refused_before_dispatch() { "windows,4c", "windows,net", "windows,wat", + "macos,4c", + "macos,net", + "macos,wat", ] { let response = handle_with_policy( &restarted_context(dir.path()), @@ -5807,9 +5830,9 @@ async fn malformed_runon_vcpu_requests_are_refused_before_dispatch() { assert!(!response.success, "{runon}: {response:?}"); let message = response.data["message"].as_str().unwrap_or_default(); assert!(message.contains(runon), "{runon}: {response:?}"); - let reason = if runon == "windows,4c" { + let reason = if runon == "windows,4c" || runon == "macos,4c" { "fixed at 4 vCPUs" - } else if runon == "windows,net" { + } else if runon == "windows,net" || runon == "macos,net" { "no network" } else { "not valid" diff --git a/crates/aft/src/exec_remote/policy.rs b/crates/aft/src/exec_remote/policy.rs index 3aef86fbe3..cf36ada584 100644 --- a/crates/aft/src/exec_remote/policy.rs +++ b/crates/aft/src/exec_remote/policy.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// Runner platforms whose demands `runon` accepts; options are parsed separately. -pub const KNOWN_DEMANDS: &[&str] = &["linux", "windows"]; +pub const KNOWN_DEMANDS: &[&str] = &["linux", "windows", "macos"]; #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -120,12 +120,17 @@ pub fn parse_demand(demand: &str) -> Result { let mut resolved = ResolvedDemand::default(); if platform == "windows" { resolved.platform = Some(super::types::Platform::Windows); + } else if platform == "macos" { + resolved.platform = Some(super::types::Platform::Macos); } for token in tokens { if token == "net" { if platform == "windows" { return Err(format!("runon {demand:?} is not valid: Windows jobs have no network; windows,net is not supported")); } + if platform == "macos" { + return Err(format!("runon {demand:?} is not valid: macOS jobs have no network; macos,net is not supported")); + } if resolved.network { return Err(format!( "runon {demand:?} is not valid: duplicate net option" @@ -146,6 +151,9 @@ pub fn parse_demand(demand: &str) -> Result { if platform == "windows" { return Err(format!("runon {demand:?} is not valid: the Windows slot is fixed at 4 vCPUs; use plain windows without a vCPU count")); } + if platform == "macos" { + return Err(format!("runon {demand:?} is not valid: the macOS slot is fixed at 4 vCPUs; use plain macos without a vCPU count")); + } if resolved.weight_hint.is_some() { return Err(format!( "runon {demand:?} is not valid: duplicate vCPU count" @@ -160,6 +168,9 @@ fn invalid_vcpu_demand(demand: &str) -> String { if demand.starts_with("windows,") { return format!("runon {demand:?} is not valid: Windows jobs accept no options; use plain windows (fixed at 4 vCPUs, no network)"); } + if demand.starts_with("macos,") { + return format!("runon {demand:?} is not valid: macOS jobs accept no options; use plain macos (fixed at 4 vCPUs, no network)"); + } format!("runon {demand:?} is not valid: options are net and a vCPU count as Nc, for example \"linux,4c,net\"") } @@ -200,6 +211,40 @@ mod tests { } } + #[test] + fn macos_demand_accepts_only_the_fixed_offline_slot() { + let resolved = resolve_demand("macos", None).unwrap(); + assert_eq!( + resolved.platform, + Some(super::super::types::Platform::Macos) + ); + assert_eq!(resolved.weight_hint, None); + assert!(!resolved.network); + assert_eq!(resolve_demand("", Some("macos")).unwrap(), resolved); + for (demand, reason) in [ + ( + "macos,4c", + "the macOS slot is fixed at 4 vCPUs; use plain macos without a vCPU count", + ), + ( + "macos,net", + "macOS jobs have no network; macos,net is not supported", + ), + ( + "macos,4c,net", + "the macOS slot is fixed at 4 vCPUs; use plain macos without a vCPU count", + ), + ( + "macos,wat", + "macOS jobs accept no options; use plain macos (fixed at 4 vCPUs, no network)", + ), + ] { + let error = resolve_demand(demand, None).unwrap_err(); + assert!(error.contains(demand), "{error}"); + assert!(error.contains(reason), "{error}"); + } + } + #[test] fn network_demand_options_parse_in_any_order_and_refuse_duplicates_by_name() { for demand in ["linux,net", "linux,4c,net", "linux,net,4c"] { diff --git a/crates/aft/src/subc_tool_remote_schemas.json b/crates/aft/src/subc_tool_remote_schemas.json index ebe0d9b258..f01f3fecf5 100644 --- a/crates/aft/src/subc_tool_remote_schemas.json +++ b/crates/aft/src/subc_tool_remote_schemas.json @@ -75,7 +75,7 @@ }, "runon": { "type": "string", - "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options)." + "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options)." } }, "required": [ @@ -160,7 +160,7 @@ }, "runon": { "type": "string", - "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options)." + "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options)." } }, "required": [ diff --git a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json index 4d543025f9..94a47552bc 100644 --- a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json +++ b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json @@ -3522,7 +3522,7 @@ }, "runon": { "type": "string", - "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options)." + "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options)." } }, "required": [ @@ -4753,7 +4753,7 @@ }, "runon": { "type": "string", - "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options)." + "description": "Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add \",net\" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options)." } }, "required": [ diff --git a/docs/tools.md b/docs/tools.md index d734062b97..6b62bc9e8c 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -306,7 +306,7 @@ recommended tool surface; experimental flags gate advanced behavior, not the too | `compressed` | boolean | Opt in/out of output compression for this call (default true; requires compression flag) | | `pty` | boolean | Run in a real PTY for interactive programs. Implies `background: true`. | | `ptyRows` / `ptyCols` | number | PTY dimensions (max 60 rows / 140 cols). Soft-ignored on non-PTY calls. | -| `runon` | string | Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). | +| `runon` | string | Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options). | Calls admitted under the `worker` catalog preset set `NEXTEST_TEST_THREADS` and `RUST_TEST_THREADS` for local bash children from `../.cargo/alfonso-test-threads` (one decimal integer plus newline), defaulting to 4 when the file is missing, invalid, or above 256; valid values through 256 are preserved. Existing inherited or per-call values take precedence, and shell command prefixes still override them; head sessions and unscoped plugin worker flags receive no defaults, and exec-remote requests never carry these variables. @@ -346,7 +346,10 @@ build server (ck-motor, reached through the Subconscious daemon's `exec-remote/v command starts. Without a count, the job gets 2 vCPUs. Add `,net` for outbound internet: `linux,net`, `linux,4c,net` and `linux,net,4c` are accepted; remote jobs are offline by default. A plan's `default_demand` may include these options too. `runon: "windows"` sends the command to a Windows -build machine and runs it in Git Bash; it does not accept vCPU or network options. Duplicate +build machine and runs it in Git Bash; it does not accept vCPU or network options. `runon: "macos"` +sends the command to a macOS machine; it takes no vCPU or network options. Use it for builds and +tests that need macOS (its sandbox, code signing or file watching) instead of running them on this +machine; anything needing a service manager agent (such as a launchd agent) or a GUI session stays local. Duplicate options, unknown tokens and unsupported platforms are refused by name before dispatch. It runs there under `bash -c` in the same working directory, with the same timeout and the same environment, minus the secret-shaped and AFT/CortexKit control variables AFT strips before any @@ -379,7 +382,7 @@ setting says. A call that sets `runon` is refused by name, and runs nowhere, when it cannot run remotely: the project turned remote runs off (`remote runs are off for this project`), the session has no remote runner (`this session has no remote runner`), the demand is not one AFT knows -(`unknown runner demand`; the server only runs Linux), an option is malformed (for example, +(`unknown runner demand`; AFT only runs Linux, macOS, and Windows jobs), an option is malformed (for example, `runon "linux,4" is not valid: options are net and a vCPU count as Nc, for example "linux,4c,net"`), or the call also sets `pty: true`, a PowerShell shell, or `sandbox: "host"`. On Windows `runon` is refused by name: remote dispatch diff --git a/packages/aft-bridge/src/bash-host-fallback.ts b/packages/aft-bridge/src/bash-host-fallback.ts index dbff8388e8..8057f1e3bd 100644 --- a/packages/aft-bridge/src/bash-host-fallback.ts +++ b/packages/aft-bridge/src/bash-host-fallback.ts @@ -17,7 +17,7 @@ export const BASH_HOST_FALLBACK_REFUSAL = "AFT module connection is down"; * the rest. Shared by the OpenCode and Pi tools and the module catalog. */ export const BASH_RUNON_DESCRIPTION = - 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options).'; + 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options).'; /** Guidance served only beside an available remote-run parameter. */ export const BASH_RUNON_GUIDANCE = diff --git a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts index da657bb645..72c6eafc29 100644 --- a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts +++ b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts @@ -43,7 +43,7 @@ describe("subc tool schemas artifact", () => { expect((bash.properties as Record).runon).toMatchObject({ type: "string" }); expect((bash.properties as Record).runon).toMatchObject({ description: - 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options).', + 'Run on a remote build server: `linux` (2 vCPUs), or `linux,Nc` for N vCPUs such as `linux,8c`; add ",net" for outbound internet (offline by default). `windows` runs the command in Git Bash on a Windows build machine (no vCPU or network options). `macos` sends the command to a macOS machine for builds and tests that need macOS (no vCPU or network options).', }); expect((bash.description as string).split(REMOTE_GUIDANCE)).toHaveLength(2); } From 4e1fb14b4a05bea1579624bd0364c80350d33d03 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:24:00 +0100 Subject: [PATCH 2/7] mason: regenerate catalog digests for new runon wording Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit fbeb7714d78e6537c02aca43106300f670ca8663) --- .../aft/tests/fixtures/tool_provider_catalog_presets.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json index 94a47552bc..82508d9d67 100644 --- a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json +++ b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json @@ -3427,8 +3427,8 @@ "powershell_available": false, "remote_runs": true, "reply": { - "generation": "e460fd0d87318283d67f60864b956db4eb22930a5e2cd68fc852f8b36c9c186d", - "catalog_digest": "e460fd0d87318283d67f60864b956db4eb22930a5e2cd68fc852f8b36c9c186d", + "generation": "0ae1a806bfbab9201a46c512e9ea657e994eec6c8512cc773cbdb28498d9c990", + "catalog_digest": "0ae1a806bfbab9201a46c512e9ea657e994eec6c8512cc773cbdb28498d9c990", "tools": [ { "name": "status", @@ -4658,8 +4658,8 @@ "powershell_available": true, "remote_runs": true, "reply": { - "generation": "6b9c4a9e3b9910a3092718c20be891567a5515c697b9da526bb3cc0c2e0f6b6b", - "catalog_digest": "6b9c4a9e3b9910a3092718c20be891567a5515c697b9da526bb3cc0c2e0f6b6b", + "generation": "07325522811da919739383677d6697119a94c65025ca992ccc4713c7ffbf046c", + "catalog_digest": "07325522811da919739383677d6697119a94c65025ca992ccc4713c7ffbf046c", "tools": [ { "name": "status", From 70381514db45fad8d378a7439a4ee308d15108c4 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:46:54 +0100 Subject: [PATCH 3/7] mason: name macos in runon guidance for macOS-only builds Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit ec9f3a8722be2e9beb38c3f63a522e2b8bf45788) --- .alfonso/release-notes/next/runon-macos.md | 2 +- crates/aft/src/subc_tool_remote_schemas.json | 4 ++-- .../fixtures/tool_provider_catalog_presets.json | 12 ++++++------ docs/tools.md | 2 +- packages/aft-bridge/src/bash-host-fallback.ts | 2 +- packages/opencode-plugin/src/__tests__/bash.test.ts | 3 +++ .../src/__tests__/subc-tool-schemas-fresh.test.ts | 2 +- packages/pi-plugin/src/__tests__/bash.test.ts | 3 +++ 8 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.alfonso/release-notes/next/runon-macos.md b/.alfonso/release-notes/next/runon-macos.md index 34c8ee52f7..36a1e202d7 100644 --- a/.alfonso/release-notes/next/runon-macos.md +++ b/.alfonso/release-notes/next/runon-macos.md @@ -1 +1 @@ -- `runon: "macos"` sends the command to a macOS machine for builds and tests that need macOS instead of running them on this machine. +bash can now send build and test commands to a macOS machine with runon: "macos" (fixed size, offline), so macOS-only tests no longer have to run on the local machine. diff --git a/crates/aft/src/subc_tool_remote_schemas.json b/crates/aft/src/subc_tool_remote_schemas.json index f01f3fecf5..92d77f1060 100644 --- a/crates/aft/src/subc_tool_remote_schemas.json +++ b/crates/aft/src/subc_tool_remote_schemas.json @@ -81,7 +81,7 @@ "required": [ "command" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; the task keeps running after the call returns, a completion reminder arrives when it exits, and bash_status reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; the task keeps running after the call returns, a completion reminder arrives when it exits, and bash_status reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." } }, "worker": { @@ -166,7 +166,7 @@ "required": [ "command" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." } } } diff --git a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json index 82508d9d67..ebafb34778 100644 --- a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json +++ b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json @@ -3427,8 +3427,8 @@ "powershell_available": false, "remote_runs": true, "reply": { - "generation": "0ae1a806bfbab9201a46c512e9ea657e994eec6c8512cc773cbdb28498d9c990", - "catalog_digest": "0ae1a806bfbab9201a46c512e9ea657e994eec6c8512cc773cbdb28498d9c990", + "generation": "a85fca69f10ad248b63d240b5ddd9f79fe49f317f0f2434f7e13559abfc29645", + "catalog_digest": "a85fca69f10ad248b63d240b5ddd9f79fe49f317f0f2434f7e13559abfc29645", "tools": [ { "name": "status", @@ -3461,7 +3461,7 @@ "capabilities": [ "shell.exec/v1" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", "reply": { "max_ms": 2040000 }, @@ -4658,8 +4658,8 @@ "powershell_available": true, "remote_runs": true, "reply": { - "generation": "07325522811da919739383677d6697119a94c65025ca992ccc4713c7ffbf046c", - "catalog_digest": "07325522811da919739383677d6697119a94c65025ca992ccc4713c7ffbf046c", + "generation": "9cb4314e93f87079d9ea6ac4b5821dad715fc9564d895b78424522474062d21f", + "catalog_digest": "9cb4314e93f87079d9ea6ac4b5821dad715fc9564d895b78424522474062d21f", "tools": [ { "name": "status", @@ -4692,7 +4692,7 @@ "capabilities": [ "shell.exec/v1" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", "reply": { "max_ms": 2040000 }, diff --git a/docs/tools.md b/docs/tools.md index 6b62bc9e8c..7af9a1bd8a 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -360,7 +360,7 @@ Explicit whole-line `runon` also requires the user-only live safety switch `bash.runon_enabled: true` (default false); projects cannot enable it. Turning that switch off hides and refuses `runon` without disabling legacy prefix routing. -When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output here. +When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output here. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build. Who is offered `runon`: diff --git a/packages/aft-bridge/src/bash-host-fallback.ts b/packages/aft-bridge/src/bash-host-fallback.ts index 8057f1e3bd..ace85c6f7b 100644 --- a/packages/aft-bridge/src/bash-host-fallback.ts +++ b/packages/aft-bridge/src/bash-host-fallback.ts @@ -21,7 +21,7 @@ export const BASH_RUNON_DESCRIPTION = /** Guidance served only beside an available remote-run parameter. */ export const BASH_RUNON_GUIDANCE = - 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; + 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; export interface BashHostFallbackOptions { command: string; diff --git a/packages/opencode-plugin/src/__tests__/bash.test.ts b/packages/opencode-plugin/src/__tests__/bash.test.ts index 786b02a6fe..ac78ba932a 100644 --- a/packages/opencode-plugin/src/__tests__/bash.test.ts +++ b/packages/opencode-plugin/src/__tests__/bash.test.ts @@ -303,6 +303,9 @@ describe("OpenCode bash adapter", () => { expect(bash.description).toContain( "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); + expect(bash.description).toContain( + '; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead', + ); expect(bash.description).toContain( "or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", ); diff --git a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts index 72c6eafc29..a26eaf5c26 100644 --- a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts +++ b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts @@ -18,7 +18,7 @@ const PRESETS_PATH = path.join(REPO_ROOT, "crates", "aft", "src", "subc_tool_pre const REMOTE_PATH = path.join(REPO_ROOT, "crates", "aft", "src", "subc_tool_remote_schemas.json"); const REMOTE_GUIDANCE = - 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; + 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; const PLACEHOLDER = JSON.stringify({ type: "object" }); diff --git a/packages/pi-plugin/src/__tests__/bash.test.ts b/packages/pi-plugin/src/__tests__/bash.test.ts index 61813eab7e..a34241cbc5 100644 --- a/packages/pi-plugin/src/__tests__/bash.test.ts +++ b/packages/pi-plugin/src/__tests__/bash.test.ts @@ -388,6 +388,9 @@ describe("bash tool adapter", () => { expect(bash.description).toContain( "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); + expect(bash.description).toContain( + '; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead', + ); expect(bash.description).toContain( "or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", ); From 2f42d28278c5670d467d68a2198d8e95ae0e53c9 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:01:52 +0100 Subject: [PATCH 4/7] mason: move macos guidance clause after the local-binary phrase Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit b83d6627d4e176c92113ffdc547e8c4aed955f8f) --- crates/aft/src/subc_tool_remote_schemas.json | 4 ++-- .../fixtures/tool_provider_catalog_presets.json | 12 ++++++------ docs/tools.md | 2 +- packages/aft-bridge/src/bash-host-fallback.ts | 2 +- .../src/__tests__/subc-tool-schemas-fresh.test.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/aft/src/subc_tool_remote_schemas.json b/crates/aft/src/subc_tool_remote_schemas.json index 92d77f1060..e04e42b0b9 100644 --- a/crates/aft/src/subc_tool_remote_schemas.json +++ b/crates/aft/src/subc_tool_remote_schemas.json @@ -81,7 +81,7 @@ "required": [ "command" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; the task keeps running after the call returns, a completion reminder arrives when it exits, and bash_status reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so auto-promote can remind you while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; the task keeps running after the call returns, a completion reminder arrives when it exits, and bash_status reports its state and output. Use bash({wait:true}) instead when the result is needed before anything else. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." } }, "worker": { @@ -166,7 +166,7 @@ "required": [ "command" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, aft_outline, or aft_zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete." } } } diff --git a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json index ebafb34778..495ad62533 100644 --- a/crates/aft/tests/fixtures/tool_provider_catalog_presets.json +++ b/crates/aft/tests/fixtures/tool_provider_catalog_presets.json @@ -3427,8 +3427,8 @@ "powershell_available": false, "remote_runs": true, "reply": { - "generation": "a85fca69f10ad248b63d240b5ddd9f79fe49f317f0f2434f7e13559abfc29645", - "catalog_digest": "a85fca69f10ad248b63d240b5ddd9f79fe49f317f0f2434f7e13559abfc29645", + "generation": "e5ff7b552fbdb37a38dd2a072811d768c25c9215417115eb102f44844336b256", + "catalog_digest": "e5ff7b552fbdb37a38dd2a072811d768c25c9215417115eb102f44844336b256", "tools": [ { "name": "status", @@ -3461,7 +3461,7 @@ "capabilities": [ "shell.exec/v1" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", "reply": { "max_ms": 2040000 }, @@ -4658,8 +4658,8 @@ "powershell_available": true, "remote_runs": true, "reply": { - "generation": "9cb4314e93f87079d9ea6ac4b5821dad715fc9564d895b78424522474062d21f", - "catalog_digest": "9cb4314e93f87079d9ea6ac4b5821dad715fc9564d895b78424522474062d21f", + "generation": "47fd95902f7ce34aa4dbcf26b846cd2f8a405d86cc9d65ddc1a60bfcb080ec53", + "catalog_digest": "47fd95902f7ce34aa4dbcf26b846cd2f8a405d86cc9d65ddc1a60bfcb080ec53", "tools": [ { "name": "status", @@ -4692,7 +4692,7 @@ "capabilities": [ "shell.exec/v1" ], - "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", + "description": "Execute shell commands. Output is compressed by default; pass compressed: false for raw output. Piped commands run verbatim and show the pipeline's output; for AFT's test/build summary, run the runner without | head, | tail, or | grep. Pipeline-failure notes cover single top-level pipelines only; multi-statement commands (`a; b | c; d`) are not instrumented, so masked failures inside them still need explicit exit-code checks. Commands run in the foreground and return inline; wait: true blocks until a long command finishes instead of auto-promoting (it blocks up to bash.worker_wait_max_ms, 30 minutes by default, then reports the command is still running; watch again to keep waiting); Any new message detaches this wait. Set `bash.detach_on_user_message: false` to keep it blocking; even then, a message containing the literal `&detach` forces detachment, and the token is stripped before delivery and the rest of the message is preserved; a token-only message becomes `(requested background detach)`. Use it when you need the result before doing anything else; keep it off otherwise so a long command moves to the background while you work. Use background: true yourself ONLY when you have other useful work to do while it runs; then wait on it with bash_watch before you report a result: a background task never wakes you, and a watch without a timeout waits up to the worker wait limit (`bash.worker_wait_max_ms`, 30 minutes by default), then reports it is still running; watch again to keep waiting. Never background a command and immediately bash_watch it (that wastes a turn for what foreground returns in one); never loop bash_status to wait. Do not change or delete files a background command uses until it finishes. A `nohup … &` launch still holds the call if the child keeps stdout/stderr; redirect both or use background:true. pty: true runs interactive programs (REPLs, TUIs), implies background, and is driven with bash_status({ outputMode: \"screen\" }) plus bash_write. Finished output expires after the task is 24 hours old and its completion has been delivered; under project-root restrictions, only the starting session can read output outside the project—copy cited lines into your report. `timeout` starts after spawn and kills the Unix process group (exit 124; Windows uses taskkill /T /F); processes that leave the group survive. When remote runs are available, put `runon: \"linux\"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.\n\nDO NOT use bash for code search or code exploration. If you are about to run grep, rg, sed, awk, find, or cat through bash to locate or read code: STOP — use the grep tool, read, outline, or zoom instead. When a list is cut, the reply ends with `shown N of M () · narrow: `; absence of that line means the list is complete.", "reply": { "max_ms": 2040000 }, diff --git a/docs/tools.md b/docs/tools.md index 7af9a1bd8a..5a6ef50dbc 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -360,7 +360,7 @@ Explicit whole-line `runon` also requires the user-only live safety switch `bash.runon_enabled: true` (default false); projects cannot enable it. Turning that switch off hides and refuses `runon` without disabling legacy prefix routing. -When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output here. +When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output here. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build. Who is offered `runon`: diff --git a/packages/aft-bridge/src/bash-host-fallback.ts b/packages/aft-bridge/src/bash-host-fallback.ts index ace85c6f7b..e7af5d555d 100644 --- a/packages/aft-bridge/src/bash-host-fallback.ts +++ b/packages/aft-bridge/src/bash-host-fallback.ts @@ -21,7 +21,7 @@ export const BASH_RUNON_DESCRIPTION = /** Guidance served only beside an available remote-run parameter. */ export const BASH_RUNON_GUIDANCE = - 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; + 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; export interface BashHostFallbackOptions { command: string; diff --git a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts index a26eaf5c26..9eb9a5e8ba 100644 --- a/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts +++ b/packages/opencode-plugin/src/__tests__/subc-tool-schemas-fresh.test.ts @@ -18,7 +18,7 @@ const PRESETS_PATH = path.join(REPO_ROOT, "crates", "aft", "src", "subc_tool_pre const REMOTE_PATH = path.join(REPO_ROOT, "crates", "aft", "src", "subc_tool_remote_schemas.json"); const REMOTE_GUIDANCE = - 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks); a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; + 'When remote runs are available, put `runon: "linux"` on build and test lines (cargo, bun test), including chains and pipes. Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine\'s own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks) or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree. Add `,Nc` to request N vCPUs (`linux,4c`); without it the job gets 2 vCPUs. The job sees exactly the CPUs it gets, so ask for what the command uses: 2c (the default) for a single test binary or script, 4c for `cargo check`/clippy on one crate, 8c for a workspace test suite, and 16c for a large release build.'; const PLACEHOLDER = JSON.stringify({ type: "object" }); From 34709418206db3e25d7495ec981de3906f851e4d Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:15:53 +0100 Subject: [PATCH 5/7] mason: pin moved macos clause inside bash guidance expectation Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit 40dcfe83ffdd531a2749838207753a6234de3553) --- packages/opencode-plugin/src/__tests__/bash.test.ts | 5 +---- packages/pi-plugin/src/__tests__/bash.test.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/opencode-plugin/src/__tests__/bash.test.ts b/packages/opencode-plugin/src/__tests__/bash.test.ts index ac78ba932a..aa9cd90c1c 100644 --- a/packages/opencode-plugin/src/__tests__/bash.test.ts +++ b/packages/opencode-plugin/src/__tests__/bash.test.ts @@ -304,10 +304,7 @@ describe("OpenCode bash adapter", () => { "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); expect(bash.description).toContain( - '; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead', - ); - expect(bash.description).toContain( - "or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", + "or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", ); }, ); diff --git a/packages/pi-plugin/src/__tests__/bash.test.ts b/packages/pi-plugin/src/__tests__/bash.test.ts index a34241cbc5..3b148d1345 100644 --- a/packages/pi-plugin/src/__tests__/bash.test.ts +++ b/packages/pi-plugin/src/__tests__/bash.test.ts @@ -389,10 +389,7 @@ describe("bash tool adapter", () => { "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); expect(bash.description).toContain( - '; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead', - ); - expect(bash.description).toContain( - "or runs a binary that already exists on this machine; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", + "or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", ); }, ); From 91a119a8db461ee2169dfeda6c963fe2ce11148c Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:19:04 +0100 Subject: [PATCH 6/7] search-quality descriptor for runon-macos Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> --- .../aft-search/slice-descriptors/land-runon-macos.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 benchmarks/aft-search/slice-descriptors/land-runon-macos.json diff --git a/benchmarks/aft-search/slice-descriptors/land-runon-macos.json b/benchmarks/aft-search/slice-descriptors/land-runon-macos.json new file mode 100644 index 0000000000..ebd574711e --- /dev/null +++ b/benchmarks/aft-search/slice-descriptors/land-runon-macos.json @@ -0,0 +1,8 @@ +{ + "targeted_mechanism": "none", + "kind": "harness", + "slice_class": "non_ranking", + "fixtures": [ + "harness-goldens" + ] +} From c1c5b2dc3ef5776e8ca9030584cb1e557ca7f580 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Tue, 27 Oct 2026 00:21:01 +0100 Subject: [PATCH 7/7] Format the two plugin bash guidance tests Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> --- packages/opencode-plugin/src/__tests__/bash.test.ts | 2 +- packages/pi-plugin/src/__tests__/bash.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode-plugin/src/__tests__/bash.test.ts b/packages/opencode-plugin/src/__tests__/bash.test.ts index aa9cd90c1c..8aa5ac63bc 100644 --- a/packages/opencode-plugin/src/__tests__/bash.test.ts +++ b/packages/opencode-plugin/src/__tests__/bash.test.ts @@ -304,7 +304,7 @@ describe("OpenCode bash adapter", () => { "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); expect(bash.description).toContain( - "or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", + 'or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.', ); }, ); diff --git a/packages/pi-plugin/src/__tests__/bash.test.ts b/packages/pi-plugin/src/__tests__/bash.test.ts index 3b148d1345..a8b6586bae 100644 --- a/packages/pi-plugin/src/__tests__/bash.test.ts +++ b/packages/pi-plugin/src/__tests__/bash.test.ts @@ -389,7 +389,7 @@ describe("bash tool adapter", () => { "Keep git, gh, interactive and file-editing commands local, and keep a line local if it needs this machine's own operating system or hardware (its sandbox, code signing, service manager, permission prompts or GUI frameworks)", ); expect(bash.description).toContain( - "or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: \"macos\" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.", + 'or runs a binary that already exists on this machine; a build or test that needs only macOS (its sandbox, code signing or file watching) can use runon: "macos" instead; building and testing still go remote, and a remote build leaves no binaries or target/ output in the local worktree.', ); }, );