Expose a child-process configuration hook on `ClientOptions` so embedders can isolate and reap the agent's process tree
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 45/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- rust
- Domain
- operating-systems
Research direction
Start with Client::start, spawn_stdio, spawn_tcp, and build_command, then trace Client::stop, Client::force_stop, and Drop for ClientInner. Define the cross-platform configuration and teardown behavior described in the acceptance criteria, preserving unchanged defaults and either reaping the created process group or exposing its identifier.
Written by the indexing model from the issue text.
Description
Summary
When the Rust SDK spawns the agent (Client::start → spawn_stdio / spawn_tcp → build_command → Command::spawn), the embedder has no way to configure the child process at fork time, and the SDK's teardown paths act only on the immediate child PID. That makes it impossible for a long-lived host embedding the SDK to:
- Guarantee the agent's whole descendant tree is reaped on shutdown or crash.
Client::stopcallschild.kill().await, andClient::force_stop/Drop for ClientInnercallchild.start_kill()— all of these signal only the immediate CLI child. Any processes the agent itself spawns (tool subprocesses, shells, MCP servers, language servers) are not part of that signal. Because the child is spawned in the embedder's own process group (nothing inbuild_commandcallsprocess_group/ sets a new session), there is no distinct process group to signal, so those descendants are reparented toinitand survive as orphans holding file handles and network connections. - Place the agent in its own process group / session so the embedder can do a clean
kill(-pgid)+ reap of the entire tree. - Drop privileges before
execve(setuid/setgid/setgroups) so the agent doesn't inherit the host's full uid/gid. - Install a parent-death signal (Linux
prctl(PR_SET_PDEATHSIG)) so a hard host crash (SIGKILL, OOM, abort) — where no RustDropruns — still tears the agent tree down instead of leaving it orphaned.
All four require running code in the child between fork and execve, or setting the standard std::os::unix::process::CommandExt knobs on the command the SDK builds. Today ClientOptions exposes none of these, and since the SDK owns the Command internally, the embedder can't reach it. (An embedder building its own std::process::Command could set these via CommandExt and then tokio::process::Command::from(..), but that lever isn't available when the SDK does the spawning.)
Why this matters
Embedders that run the agent as a long-lived, network-connected daemon on behalf of many sessions need agent subprocesses to be confined and fully reaped — no orphaned tool/MCP processes should outlive the agent, and none should outlive a host crash. Process-group / session isolation at spawn time plus a parent-death signal are the standard OS primitives for that guarantee, and they can only be set by the process that forks the child.
Current behavior (public source, for reference)
ClientOptionscarriesprogram,prefix_args,working_directory,env,env_remove,extra_args,transport, token/auth, telemetry,base_directory, etc. — no child-process/spawn configuration.build_commandbuilds atokio::process::Commandand never callsprocess_group, sets a session, drops privileges, or installs apre_exechook.Client::stop→child.kill().await(immediate child only).Client::force_stopandDrop for ClientInner→child.start_kill()(immediate child only).Client::pid()exposes the immediate child PID; there is noprocess_group()accessor.
Proposed API
Two complementary pieces; either alone is a big step, both together fully unblock the use cases above.
1. Structured, safe pass-through fields on ClientOptions
Thin wrappers over std::os::unix::process::CommandExt (Unix) and std::os::windows::process::CommandExt (Windows), applied in build_command:
pub struct ClientOptions {
// … existing fields …
/// Place the spawned agent in a process group. `Some(0)` makes the
/// child a new process-group leader (its PGID == its PID); `Some(pgid)`
/// joins an existing group. `None` (default) inherits the caller's
/// group — today's behavior. Maps to `CommandExt::process_group`.
pub process_group: Option<i32>,
/// Unix privilege drop applied before `execve`. Map to
/// `CommandExt::uid` / `gid` / `groups`.
#[cfg(unix)] pub uid: Option<u32>,
#[cfg(unix)] pub gid: Option<u32>,
#[cfg(unix)] pub groups: Option<Vec<u32>>,
/// Windows process creation flags (e.g. `CREATE_NEW_PROCESS_GROUP`),
/// OR-ed into the existing `CREATE_NO_WINDOW`. Maps to
/// `CommandExt::creation_flags`.
#[cfg(windows)] pub creation_flags: Option<u32>,
}
process_group, uid, gid, groups are all safe methods on CommandExt, so this subset needs no unsafe API surface and directly covers process-group isolation + privilege drop.
2. An escape-hatch child-setup hook
For everything the structured fields don't cover — prctl(PR_SET_PDEATHSIG), setrlimit, sandbox entry, mount/user namespace setup — expose a callback the SDK runs on the built command just before spawn:
/// Called with the fully-built command immediately before spawn, so the
/// embedder can apply platform-specific configuration (e.g. `pre_exec`,
/// resource limits, sandbox entry) that the structured fields don't cover.
pub command_customizer: Option<Arc<dyn Fn(&mut std::process::Command) + Send + Sync>>,
A command_customizer keeps the SDK from having to enumerate and re-export every platform knob; the embedder applies CommandExt::pre_exec (which is unsafe and carries the usual async-signal-safety contract) or any other config itself. If a callback surface is undesirable, an explicit pre_exec: Option<Arc<dyn Fn() -> io::Result<()> + Send + Sync>> field would also work.
3. Reap the group, not just the PID (when the SDK created one)
If the agent is spawned as a process-group leader (via field #1), the SDK's own teardown paths (stop, force_stop, Drop) should signal the process group (kill(-pgid, …) on Unix; a Job Object or GenerateConsoleCtrlEvent on Windows) rather than only the immediate child, so descendants are reaped on the paths the embedder doesn't drive directly. At minimum, add a Client::process_group() -> Option<i32> accessor so an embedder that opted into a new group can perform the group reap itself.
Alternatives considered
- Embedder wraps
programin a launcher thatsetsids / drops privileges / execs the real agent. Fragile: it fights the bundled-CLI resolution (the embedder doesn't know the extracted binary path), breaksClient::pid()semantics, andsetsid(1)isn't portable (absent on macOS). A first-class SDK hook is cleaner and portable. - Post-spawn
setpgidfrom the parent. Not possible:Client::startreturns afterexecve, andsetpgid(2)fails withEACCESonce the child has exec'd.
Acceptance
ClientOptionscan request a new process group / session for the agent, privilege drop, and arbitrary child-side setup beforeexecve.- With a new group requested, the SDK reaps the whole group on
stop/force_stop/Drop, or exposes the group id so the embedder can. - Defaults are unchanged (no new group, no privilege drop, no customizer) so existing consumers are unaffected.
- Dominant language
- Java
- Stars
- 10.5k
- Forks
- 1.5k
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 133
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from github/copilot-sdk
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
github/copilot-sdk#2709 · 1 comment ·
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
github/copilot-sdk#2673 ·
-
bug testing
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
github/copilot-sdk#2628 ·
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
github/copilot-sdk#2627 · 1 comment ·
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
github/copilot-sdk#2493 ·
All issues in github/copilot-sdk
Similar issues
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 90/100
apache/cloudstack#14222 ·
-
[BUG]茶杯方块在取茶时会引发崩溃 Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
1.0.0-alpha2 Type/Improvement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
wso2/dpdp-accelerator#272 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
infinispan/infinispan#18150 ·
-
area/frontend
Difficulty 2/5 1-3 hours Newbie friendliness 65/100