feat(acp): wrap workers at the subprocess launch boundary - #7985
Conversation
🔐 Codex Security Review
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ae9d82f8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let mut command = Command::new(executable); | ||
| command.args(&prefix[1..]).arg(worker); |
There was a problem hiding this comment.
Contain supervised wrappers on Windows
When BUZZ_ACP_LAUNCH_PREFIX is used on Windows with a launcher that supervises rather than replaces the worker—a mode explicitly permitted by the README—AcpClient tracks only this launcher process. Since process-group creation and killing are Unix-only, both shutdown and Drop kill only the launcher, leaving the actual ACP worker and its MCP descendants running after shutdown or crash replacement. Place the wrapper and worker in a kill-on-close Windows Job Object, or require a launcher-provided equivalent instead of advertising generic supervision.
AGENTS.md reference: AGENTS.md:L240-L244
Useful? React with 👍 / 👎.
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Changes requested: one P2 lifecycle blocker. Independently confirmed the existing Windows supervised-wrapper finding; keeping the detailed finding in that thread rather than duplicating it. This is an opt-in Windows regression, not a regression for unset/direct launches.
The smallest merge criterion is to reject configured prefixes on non-Unix without fallback, document that platform scope, and test refusal. Alternatively, provide per-worker process-tree containment and verify shutdown, Drop, and replacement cleanup. Desktop’s outer harness job does not protect individual worker replacement while the harness remains alive.
Reviewed head 9ae9d82f8bad58fb03c6fb11fa0e8674f66f3ff3 against base 2664d14316790a57ea44b3f97c57440b70e43436. Traced all production ACP launch callers, adapter identity/defaults, environment precedence, invalid-prefix handling, and Unix cleanup; no additional blockers found. Two independent review lanes covered input/test contracts and process lifetime.
Validation: source review and clean diff check; hosted Rust lint, unit tests, and Windows Rust checks passed. Other desktop/integration checks were still running at the snapshot. No local runtime tests or Windows reproduction performed. New wrapper tests are Unix-only and use exec-style wrappers, so green CI does not establish supervised-worker cleanup. Protected Goose UI/human acceptance and the linked Buzz-App integration remain separate validation gates.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 thanks for keeping this to the one spawn boundary. I traced every AcpClient::spawn caller, the Pi/Codex/Hermes identity paths and the invalid-prefix handling, and they hold up. two things beyond the Windows thread Carl already covered, both inline.
| use super::AcpError; | ||
| use tokio::process::Command; | ||
|
|
||
| pub(super) const PREFIX_ENV: &str = "BUZZ_ACP_LAUNCH_PREFIX"; |
There was a problem hiding this comment.
🤖 Desktop doesn't reserve this key. RESERVED_ENV_KEYS in desktop/src-tauri/src/managed_agents/reserved_env_keys.rs lists BUZZ_ACP_AGENT_COMMAND, BUZZ_ACP_AGENT_ARGS and BUZZ_ACP_MCP_COMMAND as the code-execution surface, and merged_user_env only filters what's on that list. managed_agents/runtime.rs writes the layered user env (definition → global → persona → agent) onto the harness command last. So a saved persona or agent env var can set BUZZ_ACP_LAUNCH_PREFIX and choose the executable that every worker starts through. The remote deploy path (commands/agents_deploy.rs) puts the same merged env into launch.env.
this also works against the sandbox use case. if a host sets the prefix and then applies user env on top of it, the way Desktop orders things, a saved env var can replace the launcher with ["/usr/bin/env"] and the worker runs unprotected.
could we add it to RESERVED_ENV_KEYS, with the matching assertion in managed_agents/env_vars/tests.rs, and note in the README that hosts have to apply the prefix after any user-supplied env? I think this should land with this PR, since the key only becomes dangerous once this PR ships.
| }; | ||
| cmd.envs(launch_env.iter().cloned()); | ||
| cmd.env_remove(launch::PREFIX_ENV); | ||
| let mut child = cmd.spawn()?; |
There was a problem hiding this comment.
🤖 nit, nonblocking: with a prefix set, a missing or non-executable launcher surfaces here as a bare io error (No such file or directory), which looks identical to a missing worker. adding the launcher path to the error when the prefix is set would make the fail-closed case easier to diagnose from the harness log.
9ae9d82 to
90d759f
Compare
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
90d759f to
7813e74
Compare
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Re-review: no remaining code blockers. The prior P2 Windows lifecycle finding is addressed. Reviewed head 7813e74f9c8a0fdf27437c4c5dafc0483179f4a0 against base 2664d14316790a57ea44b3f97c57440b70e43436, focusing on the delta from 9ae9d82f8.
- Fix verified: every configured prefix now fails before command construction on non-Unix, including empty/non-Unicode values; unset preserves direct launch. Independent lifecycle re-review agrees. Desktop’s shared case-insensitive reservation reaches save validation and local/remote environment composition. Executable error context introduces no new defect found.
- Existing CI verified: native Windows executed both platform-contract tests successfully and passed the new reservation regression in its full Desktop suite. Linux ACP suite: 992 passed, 3 skipped. These ran on synthetic merge
21033ec2e6beb7db9984b5f19821acdb1fade991, whose parents are the exact base/head above. No local runtime tests rerun. - Remaining gates, not code findings: Desktop Core CI was still running at closeout; protected-Goose UI and human acceptance remain pending. The previously noted Unix supervising-wrapper cleanup test gap remains optional. PR metadata is stale: Windows has passed, and the PR is non-draft despite its body saying “Draft pending human testing.” Reconcile readiness with the human acceptance checklist before merge.
This is a review comment, not approval.
…i-port * origin/main: fix(agents): stop built-in prompts from teaching sleep polling (#7992) feat(relay): add direct staff ban/timeout/delete with staff guard (#7883) fix(ci): gate security review on repo write access (#7986) feat(acp): wrap workers at the subprocess launch boundary (#7985) feat(buzz-relay): idempotent owner community deletion with quota reservation (#7969) feat(mobile): show contextual names in lists, Search and Pulse (#7896) Add Kimi Code's default install path to managed-agent binary discovery (#5997) Co-authored-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Brings in 31 upstream commits (639593b), including ACP native-steer frame-writer fixes (block#7568, block#8022), edited-message routing (block#4741), worker wrapping at launch (block#7985), BUZZ_GIT_IDENTITY (block#8024), thread roots in agent activity (block#8029) and built-in prompts without sleep polling (block#7992). Adaptations: - buzz-acp acp.rs: keep the fork's turn_output module alongside upstream's frame_writer module. - buzz-acp pool.rs: turn_started carries both the fork's triggeringRootEventIds and upstream's threadRootEventId. - buzz-acp queue.rs: drain_channel keeps upstream's withheld-steer reaction collection and still clears the cancelled-root tombstones. - buzz-acp queue.rs: task_root_event_id is now edit-aware, so the 1h cancelled-root tombstone also drops edits routed into a stopped tree. - buzz-acp base_prompt.md: keep the fork's empty-final-answer rule for bare acknowledgements, take upstream's handoff wording and no-sleep guidance. - buzz-acp tests: new `edit` field on fork-only QueuedEvent/BatchEvent tests. - desktop channels.rs: keep has_active_non_starter_channel guard inside upstream's ensure_starter_channels_inner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Arnoldinh0 <arnaudlafosse92100@gmail.com>
Summary
Replacing
BUZZ_ACP_AGENT_COMMANDwith a sandbox launcher hides the real adapter from Buzz: for example, Goose loses its defaultacpargument. Add optionalBUZZ_ACP_LAUNCH_PREFIX, a JSON argument array applied at the shared subprocess boundary, after normal worker configuration.On Unix, every spawn runs
prefix... worker args...without shell interpolation. Worker identity, environment setup, stdio and existing process cleanup are preserved. Invalid or unavailable prefixes fail launch without falling back to the worker; unset keeps direct launch on every platform. Configured prefixes fail closed on non-Unix because per-worker process-tree cleanup is unavailable. Desktop reserves the key against saved user-environment overrides, and spawn failures name the executable.Related issue
Prerequisite for Buzz-App #415. One commit directly on
main, independent of #7942–#7944. No duplicate launch-prefix PR found. Policy enforcement, verified launcher staging, protected paths and the supporting runtime pin remain Buzz-App/plugin work.Testing
9ae9d82f8: complete ACP suite (990 passed, three existing ignored), shipped local-task test, real-Goose probe, and full repository-widejust cipassed.buzz-acp runentrypoint completes a task through the wrapper and deterministic ACP peer.auth-methodsreturn identical results; the wrapper receivesgoose acpwith initially empty worker args.Repeated-spawn coverage is not a full lazy-pool wake/crash test. Protected Goose in the Buzz-App UI and human acceptance remain pending.
To try the hook after building
buzz-acp, use an installed Goose path:Expect the same authentication methods as without the prefix. Changing the prefix to
["/missing-launcher"]must fail. Draft pending human testing.